Index: stable/9/contrib/bind9/lib/dns/hmac_link.c =================================================================== --- stable/9/contrib/bind9/lib/dns/hmac_link.c (revision 287408) +++ stable/9/contrib/bind9/lib/dns/hmac_link.c (revision 287409) @@ -1,1729 +1,1741 @@ /* * Portions Copyright (C) 2004-2014 Internet Systems Consortium, Inc. ("ISC") * Portions Copyright (C) 1999-2002 Internet Software Consortium. * * Permission to use, copy, modify, and/or distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND ISC AND NETWORK ASSOCIATES DISCLAIMS * ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED * WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE * FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR * IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. * * Portions Copyright (C) 1995-2000 by Network Associates, Inc. * * Permission to use, copy, modify, and/or distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND ISC AND NETWORK ASSOCIATES DISCLAIMS * ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED * WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE * FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR * IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ /* * Principal Author: Brian Wellington * $Id: hmac_link.c,v 1.19 2011/01/11 23:47:13 tbox Exp $ */ #include #include #include #include #include #include #include #include #include #include #include #include "dst_internal.h" #include "dst_parse.h" static isc_result_t hmacmd5_fromdns(dst_key_t *key, isc_buffer_t *data); struct dst_hmacmd5_key { unsigned char key[ISC_MD5_BLOCK_LENGTH]; }; static isc_result_t getkeybits(dst_key_t *key, struct dst_private_element *element) { if (element->length != 2) return (DST_R_INVALIDPRIVATEKEY); key->key_bits = (element->data[0] << 8) + element->data[1]; return (ISC_R_SUCCESS); } static isc_result_t hmacmd5_createctx(dst_key_t *key, dst_context_t *dctx) { isc_hmacmd5_t *hmacmd5ctx; dst_hmacmd5_key_t *hkey = key->keydata.hmacmd5; hmacmd5ctx = isc_mem_get(dctx->mctx, sizeof(isc_hmacmd5_t)); if (hmacmd5ctx == NULL) return (ISC_R_NOMEMORY); - isc_hmacmd5_init(hmacmd5ctx, hkey->key, ISC_SHA1_BLOCK_LENGTH); + isc_hmacmd5_init(hmacmd5ctx, hkey->key, ISC_MD5_BLOCK_LENGTH); dctx->ctxdata.hmacmd5ctx = hmacmd5ctx; return (ISC_R_SUCCESS); } static void hmacmd5_destroyctx(dst_context_t *dctx) { isc_hmacmd5_t *hmacmd5ctx = dctx->ctxdata.hmacmd5ctx; if (hmacmd5ctx != NULL) { isc_hmacmd5_invalidate(hmacmd5ctx); isc_mem_put(dctx->mctx, hmacmd5ctx, sizeof(isc_hmacmd5_t)); dctx->ctxdata.hmacmd5ctx = NULL; } } static isc_result_t hmacmd5_adddata(dst_context_t *dctx, const isc_region_t *data) { isc_hmacmd5_t *hmacmd5ctx = dctx->ctxdata.hmacmd5ctx; isc_hmacmd5_update(hmacmd5ctx, data->base, data->length); return (ISC_R_SUCCESS); } static isc_result_t hmacmd5_sign(dst_context_t *dctx, isc_buffer_t *sig) { isc_hmacmd5_t *hmacmd5ctx = dctx->ctxdata.hmacmd5ctx; unsigned char *digest; if (isc_buffer_availablelength(sig) < ISC_MD5_DIGESTLENGTH) return (ISC_R_NOSPACE); digest = isc_buffer_used(sig); isc_hmacmd5_sign(hmacmd5ctx, digest); isc_buffer_add(sig, ISC_MD5_DIGESTLENGTH); return (ISC_R_SUCCESS); } static isc_result_t hmacmd5_verify(dst_context_t *dctx, const isc_region_t *sig) { isc_hmacmd5_t *hmacmd5ctx = dctx->ctxdata.hmacmd5ctx; if (sig->length > ISC_MD5_DIGESTLENGTH) return (DST_R_VERIFYFAILURE); if (isc_hmacmd5_verify2(hmacmd5ctx, sig->base, sig->length)) return (ISC_R_SUCCESS); else return (DST_R_VERIFYFAILURE); } static isc_boolean_t hmacmd5_compare(const dst_key_t *key1, const dst_key_t *key2) { dst_hmacmd5_key_t *hkey1, *hkey2; hkey1 = key1->keydata.hmacmd5; hkey2 = key2->keydata.hmacmd5; if (hkey1 == NULL && hkey2 == NULL) return (ISC_TRUE); else if (hkey1 == NULL || hkey2 == NULL) return (ISC_FALSE); - if (isc_safe_memcmp(hkey1->key, hkey2->key, ISC_SHA1_BLOCK_LENGTH)) + if (isc_safe_memcmp(hkey1->key, hkey2->key, ISC_MD5_BLOCK_LENGTH)) return (ISC_TRUE); else return (ISC_FALSE); } static isc_result_t hmacmd5_generate(dst_key_t *key, int pseudorandom_ok, void (*callback)(int)) { isc_buffer_t b; isc_result_t ret; unsigned int bytes; - unsigned char data[ISC_SHA1_BLOCK_LENGTH]; + unsigned char data[ISC_MD5_BLOCK_LENGTH]; UNUSED(callback); bytes = (key->key_size + 7) / 8; - if (bytes > ISC_SHA1_BLOCK_LENGTH) { - bytes = ISC_SHA1_BLOCK_LENGTH; - key->key_size = ISC_SHA1_BLOCK_LENGTH * 8; + if (bytes > ISC_MD5_BLOCK_LENGTH) { + bytes = ISC_MD5_BLOCK_LENGTH; + key->key_size = ISC_MD5_BLOCK_LENGTH * 8; } - memset(data, 0, ISC_SHA1_BLOCK_LENGTH); + memset(data, 0, ISC_MD5_BLOCK_LENGTH); ret = dst__entropy_getdata(data, bytes, ISC_TF(pseudorandom_ok != 0)); if (ret != ISC_R_SUCCESS) return (ret); isc_buffer_init(&b, data, bytes); isc_buffer_add(&b, bytes); ret = hmacmd5_fromdns(key, &b); - memset(data, 0, ISC_SHA1_BLOCK_LENGTH); + memset(data, 0, ISC_MD5_BLOCK_LENGTH); return (ret); } static isc_boolean_t hmacmd5_isprivate(const dst_key_t *key) { UNUSED(key); return (ISC_TRUE); } static void hmacmd5_destroy(dst_key_t *key) { dst_hmacmd5_key_t *hkey = key->keydata.hmacmd5; memset(hkey, 0, sizeof(dst_hmacmd5_key_t)); isc_mem_put(key->mctx, hkey, sizeof(dst_hmacmd5_key_t)); key->keydata.hmacmd5 = NULL; } static isc_result_t hmacmd5_todns(const dst_key_t *key, isc_buffer_t *data) { dst_hmacmd5_key_t *hkey; unsigned int bytes; REQUIRE(key->keydata.hmacmd5 != NULL); hkey = key->keydata.hmacmd5; bytes = (key->key_size + 7) / 8; if (isc_buffer_availablelength(data) < bytes) return (ISC_R_NOSPACE); isc_buffer_putmem(data, hkey->key, bytes); return (ISC_R_SUCCESS); } static isc_result_t hmacmd5_fromdns(dst_key_t *key, isc_buffer_t *data) { dst_hmacmd5_key_t *hkey; int keylen; isc_region_t r; isc_md5_t md5ctx; isc_buffer_remainingregion(data, &r); if (r.length == 0) return (ISC_R_SUCCESS); hkey = isc_mem_get(key->mctx, sizeof(dst_hmacmd5_key_t)); if (hkey == NULL) return (ISC_R_NOMEMORY); memset(hkey->key, 0, sizeof(hkey->key)); - if (r.length > ISC_SHA1_BLOCK_LENGTH) { + if (r.length > ISC_MD5_BLOCK_LENGTH) { isc_md5_init(&md5ctx); isc_md5_update(&md5ctx, r.base, r.length); isc_md5_final(&md5ctx, hkey->key); keylen = ISC_MD5_DIGESTLENGTH; } else { memmove(hkey->key, r.base, r.length); keylen = r.length; } key->key_size = keylen * 8; key->keydata.hmacmd5 = hkey; + isc_buffer_forward(data, r.length); + return (ISC_R_SUCCESS); } static isc_result_t hmacmd5_tofile(const dst_key_t *key, const char *directory) { int cnt = 0; dst_hmacmd5_key_t *hkey; dst_private_t priv; int bytes = (key->key_size + 7) / 8; unsigned char buf[2]; if (key->keydata.hmacmd5 == NULL) return (DST_R_NULLKEY); hkey = key->keydata.hmacmd5; priv.elements[cnt].tag = TAG_HMACMD5_KEY; priv.elements[cnt].length = bytes; priv.elements[cnt++].data = hkey->key; buf[0] = (key->key_bits >> 8) & 0xffU; buf[1] = key->key_bits & 0xffU; priv.elements[cnt].tag = TAG_HMACMD5_BITS; priv.elements[cnt].data = buf; priv.elements[cnt++].length = 2; priv.nelements = cnt; return (dst__privstruct_writefile(key, &priv, directory)); } static isc_result_t hmacmd5_parse(dst_key_t *key, isc_lex_t *lexer, dst_key_t *pub) { dst_private_t priv; isc_result_t result, tresult; isc_buffer_t b; isc_mem_t *mctx = key->mctx; unsigned int i; UNUSED(pub); /* read private key file */ result = dst__privstruct_parse(key, DST_ALG_HMACMD5, lexer, mctx, &priv); if (result != ISC_R_SUCCESS) return (result); key->key_bits = 0; for (i = 0; i < priv.nelements && result == ISC_R_SUCCESS; i++) { switch (priv.elements[i].tag) { case TAG_HMACMD5_KEY: isc_buffer_init(&b, priv.elements[i].data, priv.elements[i].length); isc_buffer_add(&b, priv.elements[i].length); tresult = hmacmd5_fromdns(key, &b); if (tresult != ISC_R_SUCCESS) result = tresult; break; case TAG_HMACMD5_BITS: tresult = getkeybits(key, &priv.elements[i]); if (tresult != ISC_R_SUCCESS) result = tresult; break; default: result = DST_R_INVALIDPRIVATEKEY; break; } } dst__privstruct_free(&priv, mctx); memset(&priv, 0, sizeof(priv)); return (result); } static dst_func_t hmacmd5_functions = { hmacmd5_createctx, hmacmd5_destroyctx, hmacmd5_adddata, hmacmd5_sign, hmacmd5_verify, NULL, /*%< verify2 */ NULL, /*%< computesecret */ hmacmd5_compare, NULL, /*%< paramcompare */ hmacmd5_generate, hmacmd5_isprivate, hmacmd5_destroy, hmacmd5_todns, hmacmd5_fromdns, hmacmd5_tofile, hmacmd5_parse, NULL, /*%< cleanup */ NULL, /*%< fromlabel */ NULL, /*%< dump */ NULL, /*%< restore */ }; isc_result_t dst__hmacmd5_init(dst_func_t **funcp) { REQUIRE(funcp != NULL); if (*funcp == NULL) *funcp = &hmacmd5_functions; return (ISC_R_SUCCESS); } static isc_result_t hmacsha1_fromdns(dst_key_t *key, isc_buffer_t *data); struct dst_hmacsha1_key { unsigned char key[ISC_SHA1_BLOCK_LENGTH]; }; static isc_result_t hmacsha1_createctx(dst_key_t *key, dst_context_t *dctx) { isc_hmacsha1_t *hmacsha1ctx; dst_hmacsha1_key_t *hkey = key->keydata.hmacsha1; hmacsha1ctx = isc_mem_get(dctx->mctx, sizeof(isc_hmacsha1_t)); if (hmacsha1ctx == NULL) return (ISC_R_NOMEMORY); isc_hmacsha1_init(hmacsha1ctx, hkey->key, ISC_SHA1_BLOCK_LENGTH); dctx->ctxdata.hmacsha1ctx = hmacsha1ctx; return (ISC_R_SUCCESS); } static void hmacsha1_destroyctx(dst_context_t *dctx) { isc_hmacsha1_t *hmacsha1ctx = dctx->ctxdata.hmacsha1ctx; if (hmacsha1ctx != NULL) { isc_hmacsha1_invalidate(hmacsha1ctx); isc_mem_put(dctx->mctx, hmacsha1ctx, sizeof(isc_hmacsha1_t)); dctx->ctxdata.hmacsha1ctx = NULL; } } static isc_result_t hmacsha1_adddata(dst_context_t *dctx, const isc_region_t *data) { isc_hmacsha1_t *hmacsha1ctx = dctx->ctxdata.hmacsha1ctx; isc_hmacsha1_update(hmacsha1ctx, data->base, data->length); return (ISC_R_SUCCESS); } static isc_result_t hmacsha1_sign(dst_context_t *dctx, isc_buffer_t *sig) { isc_hmacsha1_t *hmacsha1ctx = dctx->ctxdata.hmacsha1ctx; unsigned char *digest; if (isc_buffer_availablelength(sig) < ISC_SHA1_DIGESTLENGTH) return (ISC_R_NOSPACE); digest = isc_buffer_used(sig); isc_hmacsha1_sign(hmacsha1ctx, digest, ISC_SHA1_DIGESTLENGTH); isc_buffer_add(sig, ISC_SHA1_DIGESTLENGTH); return (ISC_R_SUCCESS); } static isc_result_t hmacsha1_verify(dst_context_t *dctx, const isc_region_t *sig) { isc_hmacsha1_t *hmacsha1ctx = dctx->ctxdata.hmacsha1ctx; if (sig->length > ISC_SHA1_DIGESTLENGTH || sig->length == 0) return (DST_R_VERIFYFAILURE); if (isc_hmacsha1_verify(hmacsha1ctx, sig->base, sig->length)) return (ISC_R_SUCCESS); else return (DST_R_VERIFYFAILURE); } static isc_boolean_t hmacsha1_compare(const dst_key_t *key1, const dst_key_t *key2) { dst_hmacsha1_key_t *hkey1, *hkey2; hkey1 = key1->keydata.hmacsha1; hkey2 = key2->keydata.hmacsha1; if (hkey1 == NULL && hkey2 == NULL) return (ISC_TRUE); else if (hkey1 == NULL || hkey2 == NULL) return (ISC_FALSE); if (isc_safe_memcmp(hkey1->key, hkey2->key, ISC_SHA1_BLOCK_LENGTH)) return (ISC_TRUE); else return (ISC_FALSE); } static isc_result_t hmacsha1_generate(dst_key_t *key, int pseudorandom_ok, void (*callback)(int)) { isc_buffer_t b; isc_result_t ret; unsigned int bytes; unsigned char data[ISC_SHA1_BLOCK_LENGTH]; UNUSED(callback); bytes = (key->key_size + 7) / 8; if (bytes > ISC_SHA1_BLOCK_LENGTH) { bytes = ISC_SHA1_BLOCK_LENGTH; key->key_size = ISC_SHA1_BLOCK_LENGTH * 8; } memset(data, 0, ISC_SHA1_BLOCK_LENGTH); ret = dst__entropy_getdata(data, bytes, ISC_TF(pseudorandom_ok != 0)); if (ret != ISC_R_SUCCESS) return (ret); isc_buffer_init(&b, data, bytes); isc_buffer_add(&b, bytes); ret = hmacsha1_fromdns(key, &b); memset(data, 0, ISC_SHA1_BLOCK_LENGTH); return (ret); } static isc_boolean_t hmacsha1_isprivate(const dst_key_t *key) { UNUSED(key); return (ISC_TRUE); } static void hmacsha1_destroy(dst_key_t *key) { dst_hmacsha1_key_t *hkey = key->keydata.hmacsha1; memset(hkey, 0, sizeof(dst_hmacsha1_key_t)); isc_mem_put(key->mctx, hkey, sizeof(dst_hmacsha1_key_t)); key->keydata.hmacsha1 = NULL; } static isc_result_t hmacsha1_todns(const dst_key_t *key, isc_buffer_t *data) { dst_hmacsha1_key_t *hkey; unsigned int bytes; REQUIRE(key->keydata.hmacsha1 != NULL); hkey = key->keydata.hmacsha1; bytes = (key->key_size + 7) / 8; if (isc_buffer_availablelength(data) < bytes) return (ISC_R_NOSPACE); isc_buffer_putmem(data, hkey->key, bytes); return (ISC_R_SUCCESS); } static isc_result_t hmacsha1_fromdns(dst_key_t *key, isc_buffer_t *data) { dst_hmacsha1_key_t *hkey; int keylen; isc_region_t r; isc_sha1_t sha1ctx; isc_buffer_remainingregion(data, &r); if (r.length == 0) return (ISC_R_SUCCESS); hkey = isc_mem_get(key->mctx, sizeof(dst_hmacsha1_key_t)); if (hkey == NULL) return (ISC_R_NOMEMORY); memset(hkey->key, 0, sizeof(hkey->key)); if (r.length > ISC_SHA1_BLOCK_LENGTH) { isc_sha1_init(&sha1ctx); isc_sha1_update(&sha1ctx, r.base, r.length); isc_sha1_final(&sha1ctx, hkey->key); keylen = ISC_SHA1_DIGESTLENGTH; } else { memmove(hkey->key, r.base, r.length); keylen = r.length; } key->key_size = keylen * 8; key->keydata.hmacsha1 = hkey; + isc_buffer_forward(data, r.length); + return (ISC_R_SUCCESS); } static isc_result_t hmacsha1_tofile(const dst_key_t *key, const char *directory) { int cnt = 0; dst_hmacsha1_key_t *hkey; dst_private_t priv; int bytes = (key->key_size + 7) / 8; unsigned char buf[2]; if (key->keydata.hmacsha1 == NULL) return (DST_R_NULLKEY); hkey = key->keydata.hmacsha1; priv.elements[cnt].tag = TAG_HMACSHA1_KEY; priv.elements[cnt].length = bytes; priv.elements[cnt++].data = hkey->key; buf[0] = (key->key_bits >> 8) & 0xffU; buf[1] = key->key_bits & 0xffU; priv.elements[cnt].tag = TAG_HMACSHA1_BITS; priv.elements[cnt].data = buf; priv.elements[cnt++].length = 2; priv.nelements = cnt; return (dst__privstruct_writefile(key, &priv, directory)); } static isc_result_t hmacsha1_parse(dst_key_t *key, isc_lex_t *lexer, dst_key_t *pub) { dst_private_t priv; isc_result_t result, tresult; isc_buffer_t b; isc_mem_t *mctx = key->mctx; unsigned int i; UNUSED(pub); /* read private key file */ result = dst__privstruct_parse(key, DST_ALG_HMACSHA1, lexer, mctx, &priv); if (result != ISC_R_SUCCESS) return (result); key->key_bits = 0; for (i = 0; i < priv.nelements; i++) { switch (priv.elements[i].tag) { case TAG_HMACSHA1_KEY: isc_buffer_init(&b, priv.elements[i].data, priv.elements[i].length); isc_buffer_add(&b, priv.elements[i].length); tresult = hmacsha1_fromdns(key, &b); if (tresult != ISC_R_SUCCESS) result = tresult; break; case TAG_HMACSHA1_BITS: tresult = getkeybits(key, &priv.elements[i]); if (tresult != ISC_R_SUCCESS) result = tresult; break; default: result = DST_R_INVALIDPRIVATEKEY; break; } } dst__privstruct_free(&priv, mctx); memset(&priv, 0, sizeof(priv)); return (result); } static dst_func_t hmacsha1_functions = { hmacsha1_createctx, hmacsha1_destroyctx, hmacsha1_adddata, hmacsha1_sign, hmacsha1_verify, NULL, /* verify2 */ NULL, /* computesecret */ hmacsha1_compare, NULL, /* paramcompare */ hmacsha1_generate, hmacsha1_isprivate, hmacsha1_destroy, hmacsha1_todns, hmacsha1_fromdns, hmacsha1_tofile, hmacsha1_parse, NULL, /* cleanup */ NULL, /* fromlabel */ NULL, /* dump */ NULL, /* restore */ }; isc_result_t dst__hmacsha1_init(dst_func_t **funcp) { REQUIRE(funcp != NULL); if (*funcp == NULL) *funcp = &hmacsha1_functions; return (ISC_R_SUCCESS); } static isc_result_t hmacsha224_fromdns(dst_key_t *key, isc_buffer_t *data); struct dst_hmacsha224_key { unsigned char key[ISC_SHA224_BLOCK_LENGTH]; }; static isc_result_t hmacsha224_createctx(dst_key_t *key, dst_context_t *dctx) { isc_hmacsha224_t *hmacsha224ctx; dst_hmacsha224_key_t *hkey = key->keydata.hmacsha224; hmacsha224ctx = isc_mem_get(dctx->mctx, sizeof(isc_hmacsha224_t)); if (hmacsha224ctx == NULL) return (ISC_R_NOMEMORY); isc_hmacsha224_init(hmacsha224ctx, hkey->key, ISC_SHA224_BLOCK_LENGTH); dctx->ctxdata.hmacsha224ctx = hmacsha224ctx; return (ISC_R_SUCCESS); } static void hmacsha224_destroyctx(dst_context_t *dctx) { isc_hmacsha224_t *hmacsha224ctx = dctx->ctxdata.hmacsha224ctx; if (hmacsha224ctx != NULL) { isc_hmacsha224_invalidate(hmacsha224ctx); isc_mem_put(dctx->mctx, hmacsha224ctx, sizeof(isc_hmacsha224_t)); dctx->ctxdata.hmacsha224ctx = NULL; } } static isc_result_t hmacsha224_adddata(dst_context_t *dctx, const isc_region_t *data) { isc_hmacsha224_t *hmacsha224ctx = dctx->ctxdata.hmacsha224ctx; isc_hmacsha224_update(hmacsha224ctx, data->base, data->length); return (ISC_R_SUCCESS); } static isc_result_t hmacsha224_sign(dst_context_t *dctx, isc_buffer_t *sig) { isc_hmacsha224_t *hmacsha224ctx = dctx->ctxdata.hmacsha224ctx; unsigned char *digest; if (isc_buffer_availablelength(sig) < ISC_SHA224_DIGESTLENGTH) return (ISC_R_NOSPACE); digest = isc_buffer_used(sig); isc_hmacsha224_sign(hmacsha224ctx, digest, ISC_SHA224_DIGESTLENGTH); isc_buffer_add(sig, ISC_SHA224_DIGESTLENGTH); return (ISC_R_SUCCESS); } static isc_result_t hmacsha224_verify(dst_context_t *dctx, const isc_region_t *sig) { isc_hmacsha224_t *hmacsha224ctx = dctx->ctxdata.hmacsha224ctx; if (sig->length > ISC_SHA224_DIGESTLENGTH || sig->length == 0) return (DST_R_VERIFYFAILURE); if (isc_hmacsha224_verify(hmacsha224ctx, sig->base, sig->length)) return (ISC_R_SUCCESS); else return (DST_R_VERIFYFAILURE); } static isc_boolean_t hmacsha224_compare(const dst_key_t *key1, const dst_key_t *key2) { dst_hmacsha224_key_t *hkey1, *hkey2; hkey1 = key1->keydata.hmacsha224; hkey2 = key2->keydata.hmacsha224; if (hkey1 == NULL && hkey2 == NULL) return (ISC_TRUE); else if (hkey1 == NULL || hkey2 == NULL) return (ISC_FALSE); if (isc_safe_memcmp(hkey1->key, hkey2->key, ISC_SHA224_BLOCK_LENGTH)) return (ISC_TRUE); else return (ISC_FALSE); } static isc_result_t hmacsha224_generate(dst_key_t *key, int pseudorandom_ok, void (*callback)(int)) { isc_buffer_t b; isc_result_t ret; unsigned int bytes; unsigned char data[ISC_SHA224_BLOCK_LENGTH]; UNUSED(callback); bytes = (key->key_size + 7) / 8; if (bytes > ISC_SHA224_BLOCK_LENGTH) { bytes = ISC_SHA224_BLOCK_LENGTH; key->key_size = ISC_SHA224_BLOCK_LENGTH * 8; } memset(data, 0, ISC_SHA224_BLOCK_LENGTH); ret = dst__entropy_getdata(data, bytes, ISC_TF(pseudorandom_ok != 0)); if (ret != ISC_R_SUCCESS) return (ret); isc_buffer_init(&b, data, bytes); isc_buffer_add(&b, bytes); ret = hmacsha224_fromdns(key, &b); memset(data, 0, ISC_SHA224_BLOCK_LENGTH); return (ret); } static isc_boolean_t hmacsha224_isprivate(const dst_key_t *key) { UNUSED(key); return (ISC_TRUE); } static void hmacsha224_destroy(dst_key_t *key) { dst_hmacsha224_key_t *hkey = key->keydata.hmacsha224; memset(hkey, 0, sizeof(dst_hmacsha224_key_t)); isc_mem_put(key->mctx, hkey, sizeof(dst_hmacsha224_key_t)); key->keydata.hmacsha224 = NULL; } static isc_result_t hmacsha224_todns(const dst_key_t *key, isc_buffer_t *data) { dst_hmacsha224_key_t *hkey; unsigned int bytes; REQUIRE(key->keydata.hmacsha224 != NULL); hkey = key->keydata.hmacsha224; bytes = (key->key_size + 7) / 8; if (isc_buffer_availablelength(data) < bytes) return (ISC_R_NOSPACE); isc_buffer_putmem(data, hkey->key, bytes); return (ISC_R_SUCCESS); } static isc_result_t hmacsha224_fromdns(dst_key_t *key, isc_buffer_t *data) { dst_hmacsha224_key_t *hkey; int keylen; isc_region_t r; isc_sha224_t sha224ctx; isc_buffer_remainingregion(data, &r); if (r.length == 0) return (ISC_R_SUCCESS); hkey = isc_mem_get(key->mctx, sizeof(dst_hmacsha224_key_t)); if (hkey == NULL) return (ISC_R_NOMEMORY); memset(hkey->key, 0, sizeof(hkey->key)); if (r.length > ISC_SHA224_BLOCK_LENGTH) { isc_sha224_init(&sha224ctx); isc_sha224_update(&sha224ctx, r.base, r.length); isc_sha224_final(hkey->key, &sha224ctx); keylen = ISC_SHA224_DIGESTLENGTH; } else { memmove(hkey->key, r.base, r.length); keylen = r.length; } key->key_size = keylen * 8; key->keydata.hmacsha224 = hkey; + isc_buffer_forward(data, r.length); + return (ISC_R_SUCCESS); } static isc_result_t hmacsha224_tofile(const dst_key_t *key, const char *directory) { int cnt = 0; dst_hmacsha224_key_t *hkey; dst_private_t priv; int bytes = (key->key_size + 7) / 8; unsigned char buf[2]; if (key->keydata.hmacsha224 == NULL) return (DST_R_NULLKEY); hkey = key->keydata.hmacsha224; priv.elements[cnt].tag = TAG_HMACSHA224_KEY; priv.elements[cnt].length = bytes; priv.elements[cnt++].data = hkey->key; buf[0] = (key->key_bits >> 8) & 0xffU; buf[1] = key->key_bits & 0xffU; priv.elements[cnt].tag = TAG_HMACSHA224_BITS; priv.elements[cnt].data = buf; priv.elements[cnt++].length = 2; priv.nelements = cnt; return (dst__privstruct_writefile(key, &priv, directory)); } static isc_result_t hmacsha224_parse(dst_key_t *key, isc_lex_t *lexer, dst_key_t *pub) { dst_private_t priv; isc_result_t result, tresult; isc_buffer_t b; isc_mem_t *mctx = key->mctx; unsigned int i; UNUSED(pub); /* read private key file */ result = dst__privstruct_parse(key, DST_ALG_HMACSHA224, lexer, mctx, &priv); if (result != ISC_R_SUCCESS) return (result); key->key_bits = 0; for (i = 0; i < priv.nelements; i++) { switch (priv.elements[i].tag) { case TAG_HMACSHA224_KEY: isc_buffer_init(&b, priv.elements[i].data, priv.elements[i].length); isc_buffer_add(&b, priv.elements[i].length); tresult = hmacsha224_fromdns(key, &b); if (tresult != ISC_R_SUCCESS) result = tresult; break; case TAG_HMACSHA224_BITS: tresult = getkeybits(key, &priv.elements[i]); if (tresult != ISC_R_SUCCESS) result = tresult; break; default: result = DST_R_INVALIDPRIVATEKEY; break; } } dst__privstruct_free(&priv, mctx); memset(&priv, 0, sizeof(priv)); return (result); } static dst_func_t hmacsha224_functions = { hmacsha224_createctx, hmacsha224_destroyctx, hmacsha224_adddata, hmacsha224_sign, hmacsha224_verify, NULL, /* verify2 */ NULL, /* computesecret */ hmacsha224_compare, NULL, /* paramcompare */ hmacsha224_generate, hmacsha224_isprivate, hmacsha224_destroy, hmacsha224_todns, hmacsha224_fromdns, hmacsha224_tofile, hmacsha224_parse, NULL, /* cleanup */ NULL, /* fromlabel */ NULL, /* dump */ NULL, /* restore */ }; isc_result_t dst__hmacsha224_init(dst_func_t **funcp) { REQUIRE(funcp != NULL); if (*funcp == NULL) *funcp = &hmacsha224_functions; return (ISC_R_SUCCESS); } static isc_result_t hmacsha256_fromdns(dst_key_t *key, isc_buffer_t *data); struct dst_hmacsha256_key { unsigned char key[ISC_SHA256_BLOCK_LENGTH]; }; static isc_result_t hmacsha256_createctx(dst_key_t *key, dst_context_t *dctx) { isc_hmacsha256_t *hmacsha256ctx; dst_hmacsha256_key_t *hkey = key->keydata.hmacsha256; hmacsha256ctx = isc_mem_get(dctx->mctx, sizeof(isc_hmacsha256_t)); if (hmacsha256ctx == NULL) return (ISC_R_NOMEMORY); isc_hmacsha256_init(hmacsha256ctx, hkey->key, ISC_SHA256_BLOCK_LENGTH); dctx->ctxdata.hmacsha256ctx = hmacsha256ctx; return (ISC_R_SUCCESS); } static void hmacsha256_destroyctx(dst_context_t *dctx) { isc_hmacsha256_t *hmacsha256ctx = dctx->ctxdata.hmacsha256ctx; if (hmacsha256ctx != NULL) { isc_hmacsha256_invalidate(hmacsha256ctx); isc_mem_put(dctx->mctx, hmacsha256ctx, sizeof(isc_hmacsha256_t)); dctx->ctxdata.hmacsha256ctx = NULL; } } static isc_result_t hmacsha256_adddata(dst_context_t *dctx, const isc_region_t *data) { isc_hmacsha256_t *hmacsha256ctx = dctx->ctxdata.hmacsha256ctx; isc_hmacsha256_update(hmacsha256ctx, data->base, data->length); return (ISC_R_SUCCESS); } static isc_result_t hmacsha256_sign(dst_context_t *dctx, isc_buffer_t *sig) { isc_hmacsha256_t *hmacsha256ctx = dctx->ctxdata.hmacsha256ctx; unsigned char *digest; if (isc_buffer_availablelength(sig) < ISC_SHA256_DIGESTLENGTH) return (ISC_R_NOSPACE); digest = isc_buffer_used(sig); isc_hmacsha256_sign(hmacsha256ctx, digest, ISC_SHA256_DIGESTLENGTH); isc_buffer_add(sig, ISC_SHA256_DIGESTLENGTH); return (ISC_R_SUCCESS); } static isc_result_t hmacsha256_verify(dst_context_t *dctx, const isc_region_t *sig) { isc_hmacsha256_t *hmacsha256ctx = dctx->ctxdata.hmacsha256ctx; if (sig->length > ISC_SHA256_DIGESTLENGTH || sig->length == 0) return (DST_R_VERIFYFAILURE); if (isc_hmacsha256_verify(hmacsha256ctx, sig->base, sig->length)) return (ISC_R_SUCCESS); else return (DST_R_VERIFYFAILURE); } static isc_boolean_t hmacsha256_compare(const dst_key_t *key1, const dst_key_t *key2) { dst_hmacsha256_key_t *hkey1, *hkey2; hkey1 = key1->keydata.hmacsha256; hkey2 = key2->keydata.hmacsha256; if (hkey1 == NULL && hkey2 == NULL) return (ISC_TRUE); else if (hkey1 == NULL || hkey2 == NULL) return (ISC_FALSE); if (isc_safe_memcmp(hkey1->key, hkey2->key, ISC_SHA256_BLOCK_LENGTH)) return (ISC_TRUE); else return (ISC_FALSE); } static isc_result_t hmacsha256_generate(dst_key_t *key, int pseudorandom_ok, void (*callback)(int)) { isc_buffer_t b; isc_result_t ret; unsigned int bytes; unsigned char data[ISC_SHA256_BLOCK_LENGTH]; UNUSED(callback); bytes = (key->key_size + 7) / 8; if (bytes > ISC_SHA256_BLOCK_LENGTH) { bytes = ISC_SHA256_BLOCK_LENGTH; key->key_size = ISC_SHA256_BLOCK_LENGTH * 8; } memset(data, 0, ISC_SHA256_BLOCK_LENGTH); ret = dst__entropy_getdata(data, bytes, ISC_TF(pseudorandom_ok != 0)); if (ret != ISC_R_SUCCESS) return (ret); isc_buffer_init(&b, data, bytes); isc_buffer_add(&b, bytes); ret = hmacsha256_fromdns(key, &b); memset(data, 0, ISC_SHA256_BLOCK_LENGTH); return (ret); } static isc_boolean_t hmacsha256_isprivate(const dst_key_t *key) { UNUSED(key); return (ISC_TRUE); } static void hmacsha256_destroy(dst_key_t *key) { dst_hmacsha256_key_t *hkey = key->keydata.hmacsha256; memset(hkey, 0, sizeof(dst_hmacsha256_key_t)); isc_mem_put(key->mctx, hkey, sizeof(dst_hmacsha256_key_t)); key->keydata.hmacsha256 = NULL; } static isc_result_t hmacsha256_todns(const dst_key_t *key, isc_buffer_t *data) { dst_hmacsha256_key_t *hkey; unsigned int bytes; REQUIRE(key->keydata.hmacsha256 != NULL); hkey = key->keydata.hmacsha256; bytes = (key->key_size + 7) / 8; if (isc_buffer_availablelength(data) < bytes) return (ISC_R_NOSPACE); isc_buffer_putmem(data, hkey->key, bytes); return (ISC_R_SUCCESS); } static isc_result_t hmacsha256_fromdns(dst_key_t *key, isc_buffer_t *data) { dst_hmacsha256_key_t *hkey; int keylen; isc_region_t r; isc_sha256_t sha256ctx; isc_buffer_remainingregion(data, &r); if (r.length == 0) return (ISC_R_SUCCESS); hkey = isc_mem_get(key->mctx, sizeof(dst_hmacsha256_key_t)); if (hkey == NULL) return (ISC_R_NOMEMORY); memset(hkey->key, 0, sizeof(hkey->key)); if (r.length > ISC_SHA256_BLOCK_LENGTH) { isc_sha256_init(&sha256ctx); isc_sha256_update(&sha256ctx, r.base, r.length); isc_sha256_final(hkey->key, &sha256ctx); keylen = ISC_SHA256_DIGESTLENGTH; } else { memmove(hkey->key, r.base, r.length); keylen = r.length; } key->key_size = keylen * 8; key->keydata.hmacsha256 = hkey; + isc_buffer_forward(data, r.length); + return (ISC_R_SUCCESS); } static isc_result_t hmacsha256_tofile(const dst_key_t *key, const char *directory) { int cnt = 0; dst_hmacsha256_key_t *hkey; dst_private_t priv; int bytes = (key->key_size + 7) / 8; unsigned char buf[2]; if (key->keydata.hmacsha256 == NULL) return (DST_R_NULLKEY); hkey = key->keydata.hmacsha256; priv.elements[cnt].tag = TAG_HMACSHA256_KEY; priv.elements[cnt].length = bytes; priv.elements[cnt++].data = hkey->key; buf[0] = (key->key_bits >> 8) & 0xffU; buf[1] = key->key_bits & 0xffU; priv.elements[cnt].tag = TAG_HMACSHA256_BITS; priv.elements[cnt].data = buf; priv.elements[cnt++].length = 2; priv.nelements = cnt; return (dst__privstruct_writefile(key, &priv, directory)); } static isc_result_t hmacsha256_parse(dst_key_t *key, isc_lex_t *lexer, dst_key_t *pub) { dst_private_t priv; isc_result_t result, tresult; isc_buffer_t b; isc_mem_t *mctx = key->mctx; unsigned int i; UNUSED(pub); /* read private key file */ result = dst__privstruct_parse(key, DST_ALG_HMACSHA256, lexer, mctx, &priv); if (result != ISC_R_SUCCESS) return (result); key->key_bits = 0; for (i = 0; i < priv.nelements; i++) { switch (priv.elements[i].tag) { case TAG_HMACSHA256_KEY: isc_buffer_init(&b, priv.elements[i].data, priv.elements[i].length); isc_buffer_add(&b, priv.elements[i].length); tresult = hmacsha256_fromdns(key, &b); if (tresult != ISC_R_SUCCESS) result = tresult; break; case TAG_HMACSHA256_BITS: tresult = getkeybits(key, &priv.elements[i]); if (tresult != ISC_R_SUCCESS) result = tresult; break; default: result = DST_R_INVALIDPRIVATEKEY; break; } } dst__privstruct_free(&priv, mctx); memset(&priv, 0, sizeof(priv)); return (result); } static dst_func_t hmacsha256_functions = { hmacsha256_createctx, hmacsha256_destroyctx, hmacsha256_adddata, hmacsha256_sign, hmacsha256_verify, NULL, /* verify2 */ NULL, /* computesecret */ hmacsha256_compare, NULL, /* paramcompare */ hmacsha256_generate, hmacsha256_isprivate, hmacsha256_destroy, hmacsha256_todns, hmacsha256_fromdns, hmacsha256_tofile, hmacsha256_parse, NULL, /* cleanup */ NULL, /* fromlabel */ NULL, /* dump */ NULL, /* restore */ }; isc_result_t dst__hmacsha256_init(dst_func_t **funcp) { REQUIRE(funcp != NULL); if (*funcp == NULL) *funcp = &hmacsha256_functions; return (ISC_R_SUCCESS); } static isc_result_t hmacsha384_fromdns(dst_key_t *key, isc_buffer_t *data); struct dst_hmacsha384_key { unsigned char key[ISC_SHA384_BLOCK_LENGTH]; }; static isc_result_t hmacsha384_createctx(dst_key_t *key, dst_context_t *dctx) { isc_hmacsha384_t *hmacsha384ctx; dst_hmacsha384_key_t *hkey = key->keydata.hmacsha384; hmacsha384ctx = isc_mem_get(dctx->mctx, sizeof(isc_hmacsha384_t)); if (hmacsha384ctx == NULL) return (ISC_R_NOMEMORY); isc_hmacsha384_init(hmacsha384ctx, hkey->key, ISC_SHA384_BLOCK_LENGTH); dctx->ctxdata.hmacsha384ctx = hmacsha384ctx; return (ISC_R_SUCCESS); } static void hmacsha384_destroyctx(dst_context_t *dctx) { isc_hmacsha384_t *hmacsha384ctx = dctx->ctxdata.hmacsha384ctx; if (hmacsha384ctx != NULL) { isc_hmacsha384_invalidate(hmacsha384ctx); isc_mem_put(dctx->mctx, hmacsha384ctx, sizeof(isc_hmacsha384_t)); dctx->ctxdata.hmacsha384ctx = NULL; } } static isc_result_t hmacsha384_adddata(dst_context_t *dctx, const isc_region_t *data) { isc_hmacsha384_t *hmacsha384ctx = dctx->ctxdata.hmacsha384ctx; isc_hmacsha384_update(hmacsha384ctx, data->base, data->length); return (ISC_R_SUCCESS); } static isc_result_t hmacsha384_sign(dst_context_t *dctx, isc_buffer_t *sig) { isc_hmacsha384_t *hmacsha384ctx = dctx->ctxdata.hmacsha384ctx; unsigned char *digest; if (isc_buffer_availablelength(sig) < ISC_SHA384_DIGESTLENGTH) return (ISC_R_NOSPACE); digest = isc_buffer_used(sig); isc_hmacsha384_sign(hmacsha384ctx, digest, ISC_SHA384_DIGESTLENGTH); isc_buffer_add(sig, ISC_SHA384_DIGESTLENGTH); return (ISC_R_SUCCESS); } static isc_result_t hmacsha384_verify(dst_context_t *dctx, const isc_region_t *sig) { isc_hmacsha384_t *hmacsha384ctx = dctx->ctxdata.hmacsha384ctx; if (sig->length > ISC_SHA384_DIGESTLENGTH || sig->length == 0) return (DST_R_VERIFYFAILURE); if (isc_hmacsha384_verify(hmacsha384ctx, sig->base, sig->length)) return (ISC_R_SUCCESS); else return (DST_R_VERIFYFAILURE); } static isc_boolean_t hmacsha384_compare(const dst_key_t *key1, const dst_key_t *key2) { dst_hmacsha384_key_t *hkey1, *hkey2; hkey1 = key1->keydata.hmacsha384; hkey2 = key2->keydata.hmacsha384; if (hkey1 == NULL && hkey2 == NULL) return (ISC_TRUE); else if (hkey1 == NULL || hkey2 == NULL) return (ISC_FALSE); if (isc_safe_memcmp(hkey1->key, hkey2->key, ISC_SHA384_BLOCK_LENGTH)) return (ISC_TRUE); else return (ISC_FALSE); } static isc_result_t hmacsha384_generate(dst_key_t *key, int pseudorandom_ok, void (*callback)(int)) { isc_buffer_t b; isc_result_t ret; unsigned int bytes; unsigned char data[ISC_SHA384_BLOCK_LENGTH]; UNUSED(callback); bytes = (key->key_size + 7) / 8; if (bytes > ISC_SHA384_BLOCK_LENGTH) { bytes = ISC_SHA384_BLOCK_LENGTH; key->key_size = ISC_SHA384_BLOCK_LENGTH * 8; } memset(data, 0, ISC_SHA384_BLOCK_LENGTH); ret = dst__entropy_getdata(data, bytes, ISC_TF(pseudorandom_ok != 0)); if (ret != ISC_R_SUCCESS) return (ret); isc_buffer_init(&b, data, bytes); isc_buffer_add(&b, bytes); ret = hmacsha384_fromdns(key, &b); memset(data, 0, ISC_SHA384_BLOCK_LENGTH); return (ret); } static isc_boolean_t hmacsha384_isprivate(const dst_key_t *key) { UNUSED(key); return (ISC_TRUE); } static void hmacsha384_destroy(dst_key_t *key) { dst_hmacsha384_key_t *hkey = key->keydata.hmacsha384; memset(hkey, 0, sizeof(dst_hmacsha384_key_t)); isc_mem_put(key->mctx, hkey, sizeof(dst_hmacsha384_key_t)); key->keydata.hmacsha384 = NULL; } static isc_result_t hmacsha384_todns(const dst_key_t *key, isc_buffer_t *data) { dst_hmacsha384_key_t *hkey; unsigned int bytes; REQUIRE(key->keydata.hmacsha384 != NULL); hkey = key->keydata.hmacsha384; bytes = (key->key_size + 7) / 8; if (isc_buffer_availablelength(data) < bytes) return (ISC_R_NOSPACE); isc_buffer_putmem(data, hkey->key, bytes); return (ISC_R_SUCCESS); } static isc_result_t hmacsha384_fromdns(dst_key_t *key, isc_buffer_t *data) { dst_hmacsha384_key_t *hkey; int keylen; isc_region_t r; isc_sha384_t sha384ctx; isc_buffer_remainingregion(data, &r); if (r.length == 0) return (ISC_R_SUCCESS); hkey = isc_mem_get(key->mctx, sizeof(dst_hmacsha384_key_t)); if (hkey == NULL) return (ISC_R_NOMEMORY); memset(hkey->key, 0, sizeof(hkey->key)); if (r.length > ISC_SHA384_BLOCK_LENGTH) { isc_sha384_init(&sha384ctx); isc_sha384_update(&sha384ctx, r.base, r.length); isc_sha384_final(hkey->key, &sha384ctx); keylen = ISC_SHA384_DIGESTLENGTH; } else { memmove(hkey->key, r.base, r.length); keylen = r.length; } key->key_size = keylen * 8; key->keydata.hmacsha384 = hkey; + isc_buffer_forward(data, r.length); + return (ISC_R_SUCCESS); } static isc_result_t hmacsha384_tofile(const dst_key_t *key, const char *directory) { int cnt = 0; dst_hmacsha384_key_t *hkey; dst_private_t priv; int bytes = (key->key_size + 7) / 8; unsigned char buf[2]; if (key->keydata.hmacsha384 == NULL) return (DST_R_NULLKEY); hkey = key->keydata.hmacsha384; priv.elements[cnt].tag = TAG_HMACSHA384_KEY; priv.elements[cnt].length = bytes; priv.elements[cnt++].data = hkey->key; buf[0] = (key->key_bits >> 8) & 0xffU; buf[1] = key->key_bits & 0xffU; priv.elements[cnt].tag = TAG_HMACSHA384_BITS; priv.elements[cnt].data = buf; priv.elements[cnt++].length = 2; priv.nelements = cnt; return (dst__privstruct_writefile(key, &priv, directory)); } static isc_result_t hmacsha384_parse(dst_key_t *key, isc_lex_t *lexer, dst_key_t *pub) { dst_private_t priv; isc_result_t result, tresult; isc_buffer_t b; isc_mem_t *mctx = key->mctx; unsigned int i; UNUSED(pub); /* read private key file */ result = dst__privstruct_parse(key, DST_ALG_HMACSHA384, lexer, mctx, &priv); if (result != ISC_R_SUCCESS) return (result); key->key_bits = 0; for (i = 0; i < priv.nelements; i++) { switch (priv.elements[i].tag) { case TAG_HMACSHA384_KEY: isc_buffer_init(&b, priv.elements[i].data, priv.elements[i].length); isc_buffer_add(&b, priv.elements[i].length); tresult = hmacsha384_fromdns(key, &b); if (tresult != ISC_R_SUCCESS) result = tresult; break; case TAG_HMACSHA384_BITS: tresult = getkeybits(key, &priv.elements[i]); if (tresult != ISC_R_SUCCESS) result = tresult; break; default: result = DST_R_INVALIDPRIVATEKEY; break; } } dst__privstruct_free(&priv, mctx); memset(&priv, 0, sizeof(priv)); return (result); } static dst_func_t hmacsha384_functions = { hmacsha384_createctx, hmacsha384_destroyctx, hmacsha384_adddata, hmacsha384_sign, hmacsha384_verify, NULL, /* verify2 */ NULL, /* computesecret */ hmacsha384_compare, NULL, /* paramcompare */ hmacsha384_generate, hmacsha384_isprivate, hmacsha384_destroy, hmacsha384_todns, hmacsha384_fromdns, hmacsha384_tofile, hmacsha384_parse, NULL, /* cleanup */ NULL, /* fromlabel */ NULL, /* dump */ NULL, /* restore */ }; isc_result_t dst__hmacsha384_init(dst_func_t **funcp) { REQUIRE(funcp != NULL); if (*funcp == NULL) *funcp = &hmacsha384_functions; return (ISC_R_SUCCESS); } static isc_result_t hmacsha512_fromdns(dst_key_t *key, isc_buffer_t *data); struct dst_hmacsha512_key { unsigned char key[ISC_SHA512_BLOCK_LENGTH]; }; static isc_result_t hmacsha512_createctx(dst_key_t *key, dst_context_t *dctx) { isc_hmacsha512_t *hmacsha512ctx; dst_hmacsha512_key_t *hkey = key->keydata.hmacsha512; hmacsha512ctx = isc_mem_get(dctx->mctx, sizeof(isc_hmacsha512_t)); if (hmacsha512ctx == NULL) return (ISC_R_NOMEMORY); isc_hmacsha512_init(hmacsha512ctx, hkey->key, ISC_SHA512_BLOCK_LENGTH); dctx->ctxdata.hmacsha512ctx = hmacsha512ctx; return (ISC_R_SUCCESS); } static void hmacsha512_destroyctx(dst_context_t *dctx) { isc_hmacsha512_t *hmacsha512ctx = dctx->ctxdata.hmacsha512ctx; if (hmacsha512ctx != NULL) { isc_hmacsha512_invalidate(hmacsha512ctx); isc_mem_put(dctx->mctx, hmacsha512ctx, sizeof(isc_hmacsha512_t)); dctx->ctxdata.hmacsha512ctx = NULL; } } static isc_result_t hmacsha512_adddata(dst_context_t *dctx, const isc_region_t *data) { isc_hmacsha512_t *hmacsha512ctx = dctx->ctxdata.hmacsha512ctx; isc_hmacsha512_update(hmacsha512ctx, data->base, data->length); return (ISC_R_SUCCESS); } static isc_result_t hmacsha512_sign(dst_context_t *dctx, isc_buffer_t *sig) { isc_hmacsha512_t *hmacsha512ctx = dctx->ctxdata.hmacsha512ctx; unsigned char *digest; if (isc_buffer_availablelength(sig) < ISC_SHA512_DIGESTLENGTH) return (ISC_R_NOSPACE); digest = isc_buffer_used(sig); isc_hmacsha512_sign(hmacsha512ctx, digest, ISC_SHA512_DIGESTLENGTH); isc_buffer_add(sig, ISC_SHA512_DIGESTLENGTH); return (ISC_R_SUCCESS); } static isc_result_t hmacsha512_verify(dst_context_t *dctx, const isc_region_t *sig) { isc_hmacsha512_t *hmacsha512ctx = dctx->ctxdata.hmacsha512ctx; if (sig->length > ISC_SHA512_DIGESTLENGTH || sig->length == 0) return (DST_R_VERIFYFAILURE); if (isc_hmacsha512_verify(hmacsha512ctx, sig->base, sig->length)) return (ISC_R_SUCCESS); else return (DST_R_VERIFYFAILURE); } static isc_boolean_t hmacsha512_compare(const dst_key_t *key1, const dst_key_t *key2) { dst_hmacsha512_key_t *hkey1, *hkey2; hkey1 = key1->keydata.hmacsha512; hkey2 = key2->keydata.hmacsha512; if (hkey1 == NULL && hkey2 == NULL) return (ISC_TRUE); else if (hkey1 == NULL || hkey2 == NULL) return (ISC_FALSE); if (isc_safe_memcmp(hkey1->key, hkey2->key, ISC_SHA512_BLOCK_LENGTH)) return (ISC_TRUE); else return (ISC_FALSE); } static isc_result_t hmacsha512_generate(dst_key_t *key, int pseudorandom_ok, void (*callback)(int)) { isc_buffer_t b; isc_result_t ret; unsigned int bytes; unsigned char data[ISC_SHA512_BLOCK_LENGTH]; UNUSED(callback); bytes = (key->key_size + 7) / 8; if (bytes > ISC_SHA512_BLOCK_LENGTH) { bytes = ISC_SHA512_BLOCK_LENGTH; key->key_size = ISC_SHA512_BLOCK_LENGTH * 8; } memset(data, 0, ISC_SHA512_BLOCK_LENGTH); ret = dst__entropy_getdata(data, bytes, ISC_TF(pseudorandom_ok != 0)); if (ret != ISC_R_SUCCESS) return (ret); isc_buffer_init(&b, data, bytes); isc_buffer_add(&b, bytes); ret = hmacsha512_fromdns(key, &b); memset(data, 0, ISC_SHA512_BLOCK_LENGTH); return (ret); } static isc_boolean_t hmacsha512_isprivate(const dst_key_t *key) { UNUSED(key); return (ISC_TRUE); } static void hmacsha512_destroy(dst_key_t *key) { dst_hmacsha512_key_t *hkey = key->keydata.hmacsha512; memset(hkey, 0, sizeof(dst_hmacsha512_key_t)); isc_mem_put(key->mctx, hkey, sizeof(dst_hmacsha512_key_t)); key->keydata.hmacsha512 = NULL; } static isc_result_t hmacsha512_todns(const dst_key_t *key, isc_buffer_t *data) { dst_hmacsha512_key_t *hkey; unsigned int bytes; REQUIRE(key->keydata.hmacsha512 != NULL); hkey = key->keydata.hmacsha512; bytes = (key->key_size + 7) / 8; if (isc_buffer_availablelength(data) < bytes) return (ISC_R_NOSPACE); isc_buffer_putmem(data, hkey->key, bytes); return (ISC_R_SUCCESS); } static isc_result_t hmacsha512_fromdns(dst_key_t *key, isc_buffer_t *data) { dst_hmacsha512_key_t *hkey; int keylen; isc_region_t r; isc_sha512_t sha512ctx; isc_buffer_remainingregion(data, &r); if (r.length == 0) return (ISC_R_SUCCESS); hkey = isc_mem_get(key->mctx, sizeof(dst_hmacsha512_key_t)); if (hkey == NULL) return (ISC_R_NOMEMORY); memset(hkey->key, 0, sizeof(hkey->key)); if (r.length > ISC_SHA512_BLOCK_LENGTH) { isc_sha512_init(&sha512ctx); isc_sha512_update(&sha512ctx, r.base, r.length); isc_sha512_final(hkey->key, &sha512ctx); keylen = ISC_SHA512_DIGESTLENGTH; } else { memmove(hkey->key, r.base, r.length); keylen = r.length; } key->key_size = keylen * 8; key->keydata.hmacsha512 = hkey; + + isc_buffer_forward(data, r.length); return (ISC_R_SUCCESS); } static isc_result_t hmacsha512_tofile(const dst_key_t *key, const char *directory) { int cnt = 0; dst_hmacsha512_key_t *hkey; dst_private_t priv; int bytes = (key->key_size + 7) / 8; unsigned char buf[2]; if (key->keydata.hmacsha512 == NULL) return (DST_R_NULLKEY); hkey = key->keydata.hmacsha512; priv.elements[cnt].tag = TAG_HMACSHA512_KEY; priv.elements[cnt].length = bytes; priv.elements[cnt++].data = hkey->key; buf[0] = (key->key_bits >> 8) & 0xffU; buf[1] = key->key_bits & 0xffU; priv.elements[cnt].tag = TAG_HMACSHA512_BITS; priv.elements[cnt].data = buf; priv.elements[cnt++].length = 2; priv.nelements = cnt; return (dst__privstruct_writefile(key, &priv, directory)); } static isc_result_t hmacsha512_parse(dst_key_t *key, isc_lex_t *lexer, dst_key_t *pub) { dst_private_t priv; isc_result_t result, tresult; isc_buffer_t b; isc_mem_t *mctx = key->mctx; unsigned int i; UNUSED(pub); /* read private key file */ result = dst__privstruct_parse(key, DST_ALG_HMACSHA512, lexer, mctx, &priv); if (result != ISC_R_SUCCESS) return (result); key->key_bits = 0; for (i = 0; i < priv.nelements; i++) { switch (priv.elements[i].tag) { case TAG_HMACSHA512_KEY: isc_buffer_init(&b, priv.elements[i].data, priv.elements[i].length); isc_buffer_add(&b, priv.elements[i].length); tresult = hmacsha512_fromdns(key, &b); if (tresult != ISC_R_SUCCESS) result = tresult; break; case TAG_HMACSHA512_BITS: tresult = getkeybits(key, &priv.elements[i]); if (tresult != ISC_R_SUCCESS) result = tresult; break; default: result = DST_R_INVALIDPRIVATEKEY; break; } } dst__privstruct_free(&priv, mctx); memset(&priv, 0, sizeof(priv)); return (result); } static dst_func_t hmacsha512_functions = { hmacsha512_createctx, hmacsha512_destroyctx, hmacsha512_adddata, hmacsha512_sign, hmacsha512_verify, NULL, /* verify2 */ NULL, /* computesecret */ hmacsha512_compare, NULL, /* paramcompare */ hmacsha512_generate, hmacsha512_isprivate, hmacsha512_destroy, hmacsha512_todns, hmacsha512_fromdns, hmacsha512_tofile, hmacsha512_parse, NULL, /* cleanup */ NULL, /* fromlabel */ NULL, /* dump */ NULL, /* restore */ }; isc_result_t dst__hmacsha512_init(dst_func_t **funcp) { REQUIRE(funcp != NULL); if (*funcp == NULL) *funcp = &hmacsha512_functions; return (ISC_R_SUCCESS); } /*! \file */ Index: stable/9/contrib/bind9/lib/dns/include/dst/dst.h =================================================================== --- stable/9/contrib/bind9/lib/dns/include/dst/dst.h (revision 287408) +++ stable/9/contrib/bind9/lib/dns/include/dst/dst.h (revision 287409) @@ -1,952 +1,953 @@ /* * Copyright (C) 2004-2013 Internet Systems Consortium, Inc. ("ISC") * Copyright (C) 2000-2002 Internet Software Consortium. * * Permission to use, copy, modify, and/or distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY * AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT, * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE * OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR * PERFORMANCE OF THIS SOFTWARE. */ /* $Id: dst.h,v 1.34 2011/10/20 21:20:02 marka Exp $ */ #ifndef DST_DST_H #define DST_DST_H 1 /*! \file dst/dst.h */ #include #include #include #include #include #include #include ISC_LANG_BEGINDECLS /*** *** Types ***/ /*% * The dst_key structure is opaque. Applications should use the accessor * functions provided to retrieve key attributes. If an application needs * to set attributes, new accessor functions will be written. */ typedef struct dst_key dst_key_t; typedef struct dst_context dst_context_t; /* DST algorithm codes */ #define DST_ALG_UNKNOWN 0 #define DST_ALG_RSAMD5 1 #define DST_ALG_RSA DST_ALG_RSAMD5 /*%< backwards compatibility */ #define DST_ALG_DH 2 #define DST_ALG_DSA 3 #define DST_ALG_ECC 4 #define DST_ALG_RSASHA1 5 #define DST_ALG_NSEC3DSA 6 #define DST_ALG_NSEC3RSASHA1 7 #define DST_ALG_RSASHA256 8 #define DST_ALG_RSASHA512 10 #define DST_ALG_ECCGOST 12 #define DST_ALG_ECDSA256 13 #define DST_ALG_ECDSA384 14 #define DST_ALG_HMACMD5 157 #define DST_ALG_GSSAPI 160 #define DST_ALG_HMACSHA1 161 /* XXXMPA */ #define DST_ALG_HMACSHA224 162 /* XXXMPA */ #define DST_ALG_HMACSHA256 163 /* XXXMPA */ #define DST_ALG_HMACSHA384 164 /* XXXMPA */ #define DST_ALG_HMACSHA512 165 /* XXXMPA */ +#define DST_ALG_INDIRECT 252 #define DST_ALG_PRIVATE 254 #define DST_ALG_EXPAND 255 #define DST_MAX_ALGS 255 /*% A buffer of this size is large enough to hold any key */ #define DST_KEY_MAXSIZE 1280 /*% * A buffer of this size is large enough to hold the textual representation * of any key */ #define DST_KEY_MAXTEXTSIZE 2048 /*% 'Type' for dst_read_key() */ #define DST_TYPE_KEY 0x1000000 /* KEY key */ #define DST_TYPE_PRIVATE 0x2000000 #define DST_TYPE_PUBLIC 0x4000000 /* Key timing metadata definitions */ #define DST_TIME_CREATED 0 #define DST_TIME_PUBLISH 1 #define DST_TIME_ACTIVATE 2 #define DST_TIME_REVOKE 3 #define DST_TIME_INACTIVE 4 #define DST_TIME_DELETE 5 #define DST_TIME_DSPUBLISH 6 #define DST_MAX_TIMES 6 /* Numeric metadata definitions */ #define DST_NUM_PREDECESSOR 0 #define DST_NUM_SUCCESSOR 1 #define DST_NUM_MAXTTL 2 #define DST_NUM_ROLLPERIOD 3 #define DST_MAX_NUMERIC 3 /* * Current format version number of the private key parser. * * When parsing a key file with the same major number but a higher minor * number, the key parser will ignore any fields it does not recognize. * Thus, DST_MINOR_VERSION should be incremented whenever new * fields are added to the private key file (such as new metadata). * * When rewriting these keys, those fields will be dropped, and the * format version set back to the current one.. * * When a key is seen with a higher major number, the key parser will * reject it as invalid. Thus, DST_MAJOR_VERSION should be incremented * and DST_MINOR_VERSION set to zero whenever there is a format change * which is not backward compatible to previous versions of the dst_key * parser, such as change in the syntax of an existing field, the removal * of a currently mandatory field, or a new field added which would * alter the functioning of the key if it were absent. */ #define DST_MAJOR_VERSION 1 #define DST_MINOR_VERSION 3 /*** *** Functions ***/ isc_result_t dst_lib_init(isc_mem_t *mctx, isc_entropy_t *ectx, unsigned int eflags); isc_result_t dst_lib_init2(isc_mem_t *mctx, isc_entropy_t *ectx, const char *engine, unsigned int eflags); /*%< * Initializes the DST subsystem. * * Requires: * \li "mctx" is a valid memory context * \li "ectx" is a valid entropy context * * Returns: * \li ISC_R_SUCCESS * \li ISC_R_NOMEMORY * \li DST_R_NOENGINE * * Ensures: * \li DST is properly initialized. */ void dst_lib_destroy(void); /*%< * Releases all resources allocated by DST. */ isc_boolean_t dst_algorithm_supported(unsigned int alg); /*%< * Checks that a given algorithm is supported by DST. * * Returns: * \li ISC_TRUE * \li ISC_FALSE */ isc_result_t dst_context_create(dst_key_t *key, isc_mem_t *mctx, dst_context_t **dctxp); isc_result_t dst_context_create2(dst_key_t *key, isc_mem_t *mctx, isc_logcategory_t *category, dst_context_t **dctxp); /*%< * Creates a context to be used for a sign or verify operation. * * Requires: * \li "key" is a valid key. * \li "mctx" is a valid memory context. * \li dctxp != NULL && *dctxp == NULL * * Returns: * \li ISC_R_SUCCESS * \li ISC_R_NOMEMORY * * Ensures: * \li *dctxp will contain a usable context. */ void dst_context_destroy(dst_context_t **dctxp); /*%< * Destroys all memory associated with a context. * * Requires: * \li *dctxp != NULL && *dctxp == NULL * * Ensures: * \li *dctxp == NULL */ isc_result_t dst_context_adddata(dst_context_t *dctx, const isc_region_t *data); /*%< * Incrementally adds data to the context to be used in a sign or verify * operation. * * Requires: * \li "dctx" is a valid context * \li "data" is a valid region * * Returns: * \li ISC_R_SUCCESS * \li DST_R_SIGNFAILURE * \li all other errors indicate failure */ isc_result_t dst_context_sign(dst_context_t *dctx, isc_buffer_t *sig); /*%< * Computes a signature using the data and key stored in the context. * * Requires: * \li "dctx" is a valid context. * \li "sig" is a valid buffer. * * Returns: * \li ISC_R_SUCCESS * \li DST_R_VERIFYFAILURE * \li all other errors indicate failure * * Ensures: * \li "sig" will contain the signature */ isc_result_t dst_context_verify(dst_context_t *dctx, isc_region_t *sig); isc_result_t dst_context_verify2(dst_context_t *dctx, unsigned int maxbits, isc_region_t *sig); /*%< * Verifies the signature using the data and key stored in the context. * * 'maxbits' specifies the maximum number of bits permitted in the RSA * exponent. * * Requires: * \li "dctx" is a valid context. * \li "sig" is a valid region. * * Returns: * \li ISC_R_SUCCESS * \li all other errors indicate failure * * Ensures: * \li "sig" will contain the signature */ isc_result_t dst_key_computesecret(const dst_key_t *pub, const dst_key_t *priv, isc_buffer_t *secret); /*%< * Computes a shared secret from two (Diffie-Hellman) keys. * * Requires: * \li "pub" is a valid key that can be used to derive a shared secret * \li "priv" is a valid private key that can be used to derive a shared secret * \li "secret" is a valid buffer * * Returns: * \li ISC_R_SUCCESS * \li any other result indicates failure * * Ensures: * \li If successful, secret will contain the derived shared secret. */ isc_result_t dst_key_fromfile(dns_name_t *name, dns_keytag_t id, unsigned int alg, int type, const char *directory, isc_mem_t *mctx, dst_key_t **keyp); /*%< * Reads a key from permanent storage. The key can either be a public or * private key, and is specified by name, algorithm, and id. If a private key * is specified, the public key must also be present. If directory is NULL, * the current directory is assumed. * * Requires: * \li "name" is a valid absolute dns name. * \li "id" is a valid key tag identifier. * \li "alg" is a supported key algorithm. * \li "type" is DST_TYPE_PUBLIC, DST_TYPE_PRIVATE, or the bitwise union. * DST_TYPE_KEY look for a KEY record otherwise DNSKEY * \li "mctx" is a valid memory context. * \li "keyp" is not NULL and "*keyp" is NULL. * * Returns: * \li ISC_R_SUCCESS * \li any other result indicates failure * * Ensures: * \li If successful, *keyp will contain a valid key. */ isc_result_t dst_key_fromnamedfile(const char *filename, const char *dirname, int type, isc_mem_t *mctx, dst_key_t **keyp); /*%< * Reads a key from permanent storage. The key can either be a public or * key, and is specified by filename. If a private key is specified, the * public key must also be present. * * If 'dirname' is not NULL, and 'filename' is a relative path, * then the file is looked up relative to the given directory. * If 'filename' is an absolute path, 'dirname' is ignored. * * Requires: * \li "filename" is not NULL * \li "type" is DST_TYPE_PUBLIC, DST_TYPE_PRIVATE, or the bitwise union * DST_TYPE_KEY look for a KEY record otherwise DNSKEY * \li "mctx" is a valid memory context * \li "keyp" is not NULL and "*keyp" is NULL. * * Returns: * \li ISC_R_SUCCESS * \li any other result indicates failure * * Ensures: * \li If successful, *keyp will contain a valid key. */ isc_result_t dst_key_read_public(const char *filename, int type, isc_mem_t *mctx, dst_key_t **keyp); /*%< * Reads a public key from permanent storage. The key must be a public key. * * Requires: * \li "filename" is not NULL * \li "type" is DST_TYPE_KEY look for a KEY record otherwise DNSKEY * \li "mctx" is a valid memory context * \li "keyp" is not NULL and "*keyp" is NULL. * * Returns: * \li ISC_R_SUCCESS * \li DST_R_BADKEYTYPE if the key type is not the expected one * \li ISC_R_UNEXPECTEDTOKEN if the file can not be parsed as a public key * \li any other result indicates failure * * Ensures: * \li If successful, *keyp will contain a valid key. */ isc_result_t dst_key_tofile(const dst_key_t *key, int type, const char *directory); /*%< * Writes a key to permanent storage. The key can either be a public or * private key. Public keys are written in DNS format and private keys * are written as a set of base64 encoded values. If directory is NULL, * the current directory is assumed. * * Requires: * \li "key" is a valid key. * \li "type" is DST_TYPE_PUBLIC, DST_TYPE_PRIVATE, or the bitwise union * * Returns: * \li ISC_R_SUCCESS * \li any other result indicates failure */ isc_result_t dst_key_fromdns(dns_name_t *name, dns_rdataclass_t rdclass, isc_buffer_t *source, isc_mem_t *mctx, dst_key_t **keyp); /*%< * Converts a DNS KEY record into a DST key. * * Requires: * \li "name" is a valid absolute dns name. * \li "source" is a valid buffer. There must be at least 4 bytes available. * \li "mctx" is a valid memory context. * \li "keyp" is not NULL and "*keyp" is NULL. * * Returns: * \li ISC_R_SUCCESS * \li any other result indicates failure * * Ensures: * \li If successful, *keyp will contain a valid key, and the consumed * pointer in data will be advanced. */ isc_result_t dst_key_todns(const dst_key_t *key, isc_buffer_t *target); /*%< * Converts a DST key into a DNS KEY record. * * Requires: * \li "key" is a valid key. * \li "target" is a valid buffer. There must be at least 4 bytes unused. * * Returns: * \li ISC_R_SUCCESS * \li any other result indicates failure * * Ensures: * \li If successful, the used pointer in 'target' is advanced by at least 4. */ isc_result_t dst_key_frombuffer(dns_name_t *name, unsigned int alg, unsigned int flags, unsigned int protocol, dns_rdataclass_t rdclass, isc_buffer_t *source, isc_mem_t *mctx, dst_key_t **keyp); /*%< * Converts a buffer containing DNS KEY RDATA into a DST key. * * Requires: *\li "name" is a valid absolute dns name. *\li "alg" is a supported key algorithm. *\li "source" is a valid buffer. *\li "mctx" is a valid memory context. *\li "keyp" is not NULL and "*keyp" is NULL. * * Returns: *\li ISC_R_SUCCESS * \li any other result indicates failure * * Ensures: *\li If successful, *keyp will contain a valid key, and the consumed * pointer in source will be advanced. */ isc_result_t dst_key_tobuffer(const dst_key_t *key, isc_buffer_t *target); /*%< * Converts a DST key into DNS KEY RDATA format. * * Requires: *\li "key" is a valid key. *\li "target" is a valid buffer. * * Returns: *\li ISC_R_SUCCESS * \li any other result indicates failure * * Ensures: *\li If successful, the used pointer in 'target' is advanced. */ isc_result_t dst_key_privatefrombuffer(dst_key_t *key, isc_buffer_t *buffer); /*%< * Converts a public key into a private key, reading the private key * information from the buffer. The buffer should contain the same data * as the .private key file would. * * Requires: *\li "key" is a valid public key. *\li "buffer" is not NULL. * * Returns: *\li ISC_R_SUCCESS * \li any other result indicates failure * * Ensures: *\li If successful, key will contain a valid private key. */ gss_ctx_id_t dst_key_getgssctx(const dst_key_t *key); /*%< * Returns the opaque key data. * Be cautions when using this value unless you know what you are doing. * * Requires: *\li "key" is not NULL. * * Returns: *\li gssctx key data, possibly NULL. */ isc_result_t dst_key_fromgssapi(dns_name_t *name, gss_ctx_id_t gssctx, isc_mem_t *mctx, dst_key_t **keyp, isc_region_t *intoken); /*%< * Converts a GSSAPI opaque context id into a DST key. * * Requires: *\li "name" is a valid absolute dns name. *\li "gssctx" is a GSSAPI context id. *\li "mctx" is a valid memory context. *\li "keyp" is not NULL and "*keyp" is NULL. * * Returns: *\li ISC_R_SUCCESS * \li any other result indicates failure * * Ensures: *\li If successful, *keyp will contain a valid key and be responsible for * the context id. */ #ifdef DST_KEY_INTERNAL isc_result_t dst_key_buildinternal(dns_name_t *name, unsigned int alg, unsigned int bits, unsigned int flags, unsigned int protocol, dns_rdataclass_t rdclass, void *data, isc_mem_t *mctx, dst_key_t **keyp); #endif isc_result_t dst_key_fromlabel(dns_name_t *name, int alg, unsigned int flags, unsigned int protocol, dns_rdataclass_t rdclass, const char *engine, const char *label, const char *pin, isc_mem_t *mctx, dst_key_t **keyp); isc_result_t dst_key_generate(dns_name_t *name, unsigned int alg, unsigned int bits, unsigned int param, unsigned int flags, unsigned int protocol, dns_rdataclass_t rdclass, isc_mem_t *mctx, dst_key_t **keyp); isc_result_t dst_key_generate2(dns_name_t *name, unsigned int alg, unsigned int bits, unsigned int param, unsigned int flags, unsigned int protocol, dns_rdataclass_t rdclass, isc_mem_t *mctx, dst_key_t **keyp, void (*callback)(int)); /*%< * Generate a DST key (or keypair) with the supplied parameters. The * interpretation of the "param" field depends on the algorithm: * \code * RSA: exponent * 0 use exponent 3 * !0 use Fermat4 (2^16 + 1) * DH: generator * 0 default - use well known prime if bits == 768 or 1024, * otherwise use 2 as the generator. * !0 use this value as the generator. * DSA: unused * HMACMD5: entropy * 0 default - require good entropy * !0 lack of good entropy is ok *\endcode * * Requires: *\li "name" is a valid absolute dns name. *\li "keyp" is not NULL and "*keyp" is NULL. * * Returns: *\li ISC_R_SUCCESS * \li any other result indicates failure * * Ensures: *\li If successful, *keyp will contain a valid key. */ isc_boolean_t dst_key_compare(const dst_key_t *key1, const dst_key_t *key2); /*%< * Compares two DST keys. Returns true if they match, false otherwise. * * Keys ARE NOT considered to match if one of them is the revoked version * of the other. * * Requires: *\li "key1" is a valid key. *\li "key2" is a valid key. * * Returns: *\li ISC_TRUE * \li ISC_FALSE */ isc_boolean_t dst_key_pubcompare(const dst_key_t *key1, const dst_key_t *key2, isc_boolean_t match_revoked_key); /*%< * Compares only the public portions of two DST keys. Returns true * if they match, false otherwise. This allows us, for example, to * determine whether a public key found in a zone matches up with a * key pair found on disk. * * If match_revoked_key is TRUE, then keys ARE considered to match if one * of them is the revoked version of the other. Otherwise, they are not. * * Requires: *\li "key1" is a valid key. *\li "key2" is a valid key. * * Returns: *\li ISC_TRUE * \li ISC_FALSE */ isc_boolean_t dst_key_paramcompare(const dst_key_t *key1, const dst_key_t *key2); /*%< * Compares the parameters of two DST keys. This is used to determine if * two (Diffie-Hellman) keys can be used to derive a shared secret. * * Requires: *\li "key1" is a valid key. *\li "key2" is a valid key. * * Returns: *\li ISC_TRUE * \li ISC_FALSE */ void dst_key_attach(dst_key_t *source, dst_key_t **target); /* * Attach to a existing key increasing the reference count. * * Requires: *\li 'source' to be a valid key. *\li 'target' to be non-NULL and '*target' to be NULL. */ void dst_key_free(dst_key_t **keyp); /*%< * Decrement the key's reference counter and, when it reaches zero, * release all memory associated with the key. * * Requires: *\li "keyp" is not NULL and "*keyp" is a valid key. *\li reference counter greater than zero. * * Ensures: *\li All memory associated with "*keyp" will be freed. *\li *keyp == NULL */ /*%< * Accessor functions to obtain key fields. * * Require: *\li "key" is a valid key. */ dns_name_t * dst_key_name(const dst_key_t *key); unsigned int dst_key_size(const dst_key_t *key); unsigned int dst_key_proto(const dst_key_t *key); unsigned int dst_key_alg(const dst_key_t *key); isc_uint32_t dst_key_flags(const dst_key_t *key); dns_keytag_t dst_key_id(const dst_key_t *key); dns_keytag_t dst_key_rid(const dst_key_t *key); dns_rdataclass_t dst_key_class(const dst_key_t *key); isc_boolean_t dst_key_isprivate(const dst_key_t *key); isc_boolean_t dst_key_iszonekey(const dst_key_t *key); isc_boolean_t dst_key_isnullkey(const dst_key_t *key); isc_result_t dst_key_buildfilename(const dst_key_t *key, int type, const char *directory, isc_buffer_t *out); /*%< * Generates the filename used by dst to store the specified key. * If directory is NULL, the current directory is assumed. * * Requires: *\li "key" is a valid key *\li "type" is either DST_TYPE_PUBLIC, DST_TYPE_PRIVATE, or 0 for no suffix. *\li "out" is a valid buffer * * Ensures: *\li the file name will be written to "out", and the used pointer will * be advanced. */ isc_result_t dst_key_sigsize(const dst_key_t *key, unsigned int *n); /*%< * Computes the size of a signature generated by the given key. * * Requires: *\li "key" is a valid key. *\li "n" is not NULL * * Returns: *\li #ISC_R_SUCCESS *\li DST_R_UNSUPPORTEDALG * * Ensures: *\li "n" stores the size of a generated signature */ isc_result_t dst_key_secretsize(const dst_key_t *key, unsigned int *n); /*%< * Computes the size of a shared secret generated by the given key. * * Requires: *\li "key" is a valid key. *\li "n" is not NULL * * Returns: *\li #ISC_R_SUCCESS *\li DST_R_UNSUPPORTEDALG * * Ensures: *\li "n" stores the size of a generated shared secret */ isc_uint16_t dst_region_computeid(const isc_region_t *source, unsigned int alg); isc_uint16_t dst_region_computerid(const isc_region_t *source, unsigned int alg); /*%< * Computes the (revoked) key id of the key stored in the provided * region with the given algorithm. * * Requires: *\li "source" contains a valid, non-NULL region. * * Returns: *\li the key id */ isc_uint16_t dst_key_getbits(const dst_key_t *key); /*%< * Get the number of digest bits required (0 == MAX). * * Requires: * "key" is a valid key. */ void dst_key_setbits(dst_key_t *key, isc_uint16_t bits); /*%< * Set the number of digest bits required (0 == MAX). * * Requires: * "key" is a valid key. */ void dst_key_setttl(dst_key_t *key, dns_ttl_t ttl); /*%< * Set the default TTL to use when converting the key * to a KEY or DNSKEY RR. * * Requires: * "key" is a valid key. */ dns_ttl_t dst_key_getttl(const dst_key_t *key); /*%< * Get the default TTL to use when converting the key * to a KEY or DNSKEY RR. * * Requires: * "key" is a valid key. */ isc_result_t dst_key_setflags(dst_key_t *key, isc_uint32_t flags); /* * Set the key flags, and recompute the key ID. * * Requires: * "key" is a valid key. */ isc_result_t dst_key_getnum(const dst_key_t *key, int type, isc_uint32_t *valuep); /*%< * Get a member of the numeric metadata array and place it in '*valuep'. * * Requires: * "key" is a valid key. * "type" is no larger than DST_MAX_NUMERIC * "timep" is not null. */ void dst_key_setnum(dst_key_t *key, int type, isc_uint32_t value); /*%< * Set a member of the numeric metadata array. * * Requires: * "key" is a valid key. * "type" is no larger than DST_MAX_NUMERIC */ void dst_key_unsetnum(dst_key_t *key, int type); /*%< * Flag a member of the numeric metadata array as "not set". * * Requires: * "key" is a valid key. * "type" is no larger than DST_MAX_NUMERIC */ isc_result_t dst_key_gettime(const dst_key_t *key, int type, isc_stdtime_t *timep); /*%< * Get a member of the timing metadata array and place it in '*timep'. * * Requires: * "key" is a valid key. * "type" is no larger than DST_MAX_TIMES * "timep" is not null. */ void dst_key_settime(dst_key_t *key, int type, isc_stdtime_t when); /*%< * Set a member of the timing metadata array. * * Requires: * "key" is a valid key. * "type" is no larger than DST_MAX_TIMES */ void dst_key_unsettime(dst_key_t *key, int type); /*%< * Flag a member of the timing metadata array as "not set". * * Requires: * "key" is a valid key. * "type" is no larger than DST_MAX_TIMES */ isc_result_t dst_key_getprivateformat(const dst_key_t *key, int *majorp, int *minorp); /*%< * Get the private key format version number. (If the key does not have * a private key associated with it, the version will be 0.0.) The major * version number is placed in '*majorp', and the minor version number in * '*minorp'. * * Requires: * "key" is a valid key. * "majorp" is not NULL. * "minorp" is not NULL. */ void dst_key_setprivateformat(dst_key_t *key, int major, int minor); /*%< * Set the private key format version number. * * Requires: * "key" is a valid key. */ #define DST_KEY_FORMATSIZE (DNS_NAME_FORMATSIZE + DNS_SECALG_FORMATSIZE + 7) void dst_key_format(const dst_key_t *key, char *cp, unsigned int size); /*%< * Write the uniquely identifying information about the key (name, * algorithm, key ID) into a string 'cp' of size 'size'. */ isc_buffer_t * dst_key_tkeytoken(const dst_key_t *key); /*%< * Return the token from the TKEY request, if any. If this key was * not negotiated via TKEY, return NULL. * * Requires: * "key" is a valid key. */ isc_result_t dst_key_dump(dst_key_t *key, isc_mem_t *mctx, char **buffer, int *length); /*%< * Allocate 'buffer' and dump the key into it in base64 format. The buffer * is not NUL terminated. The length of the buffer is returned in *length. * * 'buffer' needs to be freed using isc_mem_put(mctx, buffer, length); * * Requires: * 'buffer' to be non NULL and *buffer to be NULL. * 'length' to be non NULL and *length to be zero. * * Returns: * ISC_R_SUCCESS * ISC_R_NOMEMORY * ISC_R_NOTIMPLEMENTED * others. */ isc_result_t dst_key_restore(dns_name_t *name, unsigned int alg, unsigned int flags, unsigned int protocol, dns_rdataclass_t rdclass, isc_mem_t *mctx, const char *keystr, dst_key_t **keyp); isc_boolean_t dst_key_inactive(const dst_key_t *key); /*%< * Determines if the private key is missing due the key being deemed inactive. * * Requires: * 'key' to be valid. */ void dst_key_setinactive(dst_key_t *key, isc_boolean_t inactive); /*%< * Set key inactive state. * * Requires: * 'key' to be valid. */ void dst_key_setexternal(dst_key_t *key, isc_boolean_t value); isc_boolean_t dst_key_isexternal(dst_key_t *key); ISC_LANG_ENDDECLS #endif /* DST_DST_H */ Index: stable/9/contrib/bind9/lib/dns/ncache.c =================================================================== --- stable/9/contrib/bind9/lib/dns/ncache.c (revision 287408) +++ stable/9/contrib/bind9/lib/dns/ncache.c (revision 287409) @@ -1,756 +1,753 @@ /* * Copyright (C) 2004, 2005, 2007, 2008, 2010-2013 Internet Systems Consortium, Inc. ("ISC") * Copyright (C) 1999-2003 Internet Software Consortium. * * Permission to use, copy, modify, and/or distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY * AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT, * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE * OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR * PERFORMANCE OF THIS SOFTWARE. */ /* $Id$ */ /*! \file */ #include #include #include #include #include #include #include #include #include #include #define DNS_NCACHE_RDATA 20U /* * The format of an ncache rdata is a sequence of zero or more records of * the following format: * * owner name * type * trust * rdata count * rdata length These two occur 'rdata count' * rdata times. * */ static isc_result_t addoptout(dns_message_t *message, dns_db_t *cache, dns_dbnode_t *node, dns_rdatatype_t covers, isc_stdtime_t now, dns_ttl_t maxttl, isc_boolean_t optout, isc_boolean_t secure, dns_rdataset_t *addedrdataset); static inline isc_result_t copy_rdataset(dns_rdataset_t *rdataset, isc_buffer_t *buffer) { isc_result_t result; unsigned int count; isc_region_t ar, r; dns_rdata_t rdata = DNS_RDATA_INIT; /* * Copy the rdataset count to the buffer. */ isc_buffer_availableregion(buffer, &ar); if (ar.length < 2) return (ISC_R_NOSPACE); count = dns_rdataset_count(rdataset); INSIST(count <= 65535); isc_buffer_putuint16(buffer, (isc_uint16_t)count); result = dns_rdataset_first(rdataset); while (result == ISC_R_SUCCESS) { dns_rdataset_current(rdataset, &rdata); dns_rdata_toregion(&rdata, &r); INSIST(r.length <= 65535); isc_buffer_availableregion(buffer, &ar); if (ar.length < 2) return (ISC_R_NOSPACE); /* * Copy the rdata length to the buffer. */ isc_buffer_putuint16(buffer, (isc_uint16_t)r.length); /* * Copy the rdata to the buffer. */ result = isc_buffer_copyregion(buffer, &r); if (result != ISC_R_SUCCESS) return (result); dns_rdata_reset(&rdata); result = dns_rdataset_next(rdataset); } if (result != ISC_R_NOMORE) return (result); return (ISC_R_SUCCESS); } isc_result_t dns_ncache_add(dns_message_t *message, dns_db_t *cache, dns_dbnode_t *node, dns_rdatatype_t covers, isc_stdtime_t now, dns_ttl_t maxttl, dns_rdataset_t *addedrdataset) { return (addoptout(message, cache, node, covers, now, maxttl, ISC_FALSE, ISC_FALSE, addedrdataset)); } isc_result_t dns_ncache_addoptout(dns_message_t *message, dns_db_t *cache, dns_dbnode_t *node, dns_rdatatype_t covers, isc_stdtime_t now, dns_ttl_t maxttl, isc_boolean_t optout, dns_rdataset_t *addedrdataset) { return (addoptout(message, cache, node, covers, now, maxttl, optout, ISC_TRUE, addedrdataset)); } static isc_result_t addoptout(dns_message_t *message, dns_db_t *cache, dns_dbnode_t *node, dns_rdatatype_t covers, isc_stdtime_t now, dns_ttl_t maxttl, isc_boolean_t optout, isc_boolean_t secure, dns_rdataset_t *addedrdataset) { isc_result_t result; isc_buffer_t buffer; isc_region_t r; dns_rdataset_t *rdataset; dns_rdatatype_t type; dns_name_t *name; dns_ttl_t ttl; dns_trust_t trust; dns_rdata_t rdata[DNS_NCACHE_RDATA]; dns_rdataset_t ncrdataset; dns_rdatalist_t ncrdatalist; unsigned char data[4096]; unsigned int next = 0; /* * Convert the authority data from 'message' into a negative cache * rdataset, and store it in 'cache' at 'node'. */ REQUIRE(message != NULL); /* * We assume that all data in the authority section has been * validated by the caller. */ /* * Initialize the list. */ ncrdatalist.rdclass = dns_db_class(cache); ncrdatalist.type = 0; ncrdatalist.covers = covers; ncrdatalist.ttl = maxttl; ISC_LIST_INIT(ncrdatalist.rdata); ISC_LINK_INIT(&ncrdatalist, link); /* * Build an ncache rdatas into buffer. */ ttl = maxttl; trust = 0xffff; isc_buffer_init(&buffer, data, sizeof(data)); if (message->counts[DNS_SECTION_AUTHORITY]) result = dns_message_firstname(message, DNS_SECTION_AUTHORITY); else result = ISC_R_NOMORE; while (result == ISC_R_SUCCESS) { name = NULL; dns_message_currentname(message, DNS_SECTION_AUTHORITY, &name); if ((name->attributes & DNS_NAMEATTR_NCACHE) != 0) { for (rdataset = ISC_LIST_HEAD(name->list); rdataset != NULL; rdataset = ISC_LIST_NEXT(rdataset, link)) { if ((rdataset->attributes & DNS_RDATASETATTR_NCACHE) == 0) continue; type = rdataset->type; if (type == dns_rdatatype_rrsig) type = rdataset->covers; if (type == dns_rdatatype_soa || type == dns_rdatatype_nsec || type == dns_rdatatype_nsec3) { if (ttl > rdataset->ttl) ttl = rdataset->ttl; if (trust > rdataset->trust) trust = rdataset->trust; /* * Copy the owner name to the buffer. */ dns_name_toregion(name, &r); result = isc_buffer_copyregion(&buffer, &r); if (result != ISC_R_SUCCESS) return (result); /* * Copy the type to the buffer. */ isc_buffer_availableregion(&buffer, &r); if (r.length < 3) return (ISC_R_NOSPACE); isc_buffer_putuint16(&buffer, rdataset->type); isc_buffer_putuint8(&buffer, (unsigned char)rdataset->trust); /* * Copy the rdataset into the buffer. */ result = copy_rdataset(rdataset, &buffer); if (result != ISC_R_SUCCESS) return (result); if (next >= DNS_NCACHE_RDATA) return (ISC_R_NOSPACE); dns_rdata_init(&rdata[next]); isc_buffer_remainingregion(&buffer, &r); rdata[next].data = r.base; rdata[next].length = r.length; rdata[next].rdclass = ncrdatalist.rdclass; rdata[next].type = 0; rdata[next].flags = 0; ISC_LIST_APPEND(ncrdatalist.rdata, &rdata[next], link); isc_buffer_forward(&buffer, r.length); next++; } } } result = dns_message_nextname(message, DNS_SECTION_AUTHORITY); } if (result != ISC_R_NOMORE) return (result); if (trust == 0xffff) { if ((message->flags & DNS_MESSAGEFLAG_AA) != 0 && message->counts[DNS_SECTION_ANSWER] == 0) { /* * The response has aa set and we haven't followed * any CNAME or DNAME chains. */ trust = dns_trust_authauthority; } else trust = dns_trust_additional; ttl = 0; } INSIST(trust != 0xffff); ncrdatalist.ttl = ttl; dns_rdataset_init(&ncrdataset); RUNTIME_CHECK(dns_rdatalist_tordataset(&ncrdatalist, &ncrdataset) == ISC_R_SUCCESS); if (!secure && trust > dns_trust_answer) trust = dns_trust_answer; ncrdataset.trust = trust; ncrdataset.attributes |= DNS_RDATASETATTR_NEGATIVE; if (message->rcode == dns_rcode_nxdomain) ncrdataset.attributes |= DNS_RDATASETATTR_NXDOMAIN; if (optout) ncrdataset.attributes |= DNS_RDATASETATTR_OPTOUT; return (dns_db_addrdataset(cache, node, NULL, now, &ncrdataset, 0, addedrdataset)); } isc_result_t dns_ncache_towire(dns_rdataset_t *rdataset, dns_compress_t *cctx, isc_buffer_t *target, unsigned int options, unsigned int *countp) { dns_rdata_t rdata = DNS_RDATA_INIT; isc_result_t result; isc_region_t remaining, tavailable; isc_buffer_t source, savedbuffer, rdlen; dns_name_t name; dns_rdatatype_t type; unsigned int i, rcount, count; /* * Convert the negative caching rdataset 'rdataset' to wire format, * compressing names as specified in 'cctx', and storing the result in * 'target'. */ REQUIRE(rdataset != NULL); REQUIRE(rdataset->type == 0); REQUIRE((rdataset->attributes & DNS_RDATASETATTR_NEGATIVE) != 0); savedbuffer = *target; count = 0; result = dns_rdataset_first(rdataset); while (result == ISC_R_SUCCESS) { dns_rdataset_current(rdataset, &rdata); isc_buffer_init(&source, rdata.data, rdata.length); isc_buffer_add(&source, rdata.length); dns_name_init(&name, NULL); isc_buffer_remainingregion(&source, &remaining); dns_name_fromregion(&name, &remaining); INSIST(remaining.length >= name.length); isc_buffer_forward(&source, name.length); remaining.length -= name.length; INSIST(remaining.length >= 5); type = isc_buffer_getuint16(&source); isc_buffer_forward(&source, 1); rcount = isc_buffer_getuint16(&source); for (i = 0; i < rcount; i++) { /* * Get the length of this rdata and set up an * rdata structure for it. */ isc_buffer_remainingregion(&source, &remaining); INSIST(remaining.length >= 2); dns_rdata_reset(&rdata); rdata.length = isc_buffer_getuint16(&source); isc_buffer_remainingregion(&source, &remaining); rdata.data = remaining.base; rdata.type = type; rdata.rdclass = rdataset->rdclass; INSIST(remaining.length >= rdata.length); isc_buffer_forward(&source, rdata.length); if ((options & DNS_NCACHETOWIRE_OMITDNSSEC) != 0 && dns_rdatatype_isdnssec(type)) continue; /* * Write the name. */ dns_compress_setmethods(cctx, DNS_COMPRESS_GLOBAL14); result = dns_name_towire(&name, cctx, target); if (result != ISC_R_SUCCESS) goto rollback; /* * See if we have space for type, class, ttl, and * rdata length. Write the type, class, and ttl. */ isc_buffer_availableregion(target, &tavailable); if (tavailable.length < 10) { result = ISC_R_NOSPACE; goto rollback; } isc_buffer_putuint16(target, type); isc_buffer_putuint16(target, rdataset->rdclass); isc_buffer_putuint32(target, rdataset->ttl); /* * Save space for rdata length. */ rdlen = *target; isc_buffer_add(target, 2); /* * Write the rdata. */ result = dns_rdata_towire(&rdata, cctx, target); if (result != ISC_R_SUCCESS) goto rollback; /* * Set the rdata length field to the compressed * length. */ INSIST((target->used >= rdlen.used + 2) && (target->used - rdlen.used - 2 < 65536)); isc_buffer_putuint16(&rdlen, (isc_uint16_t)(target->used - rdlen.used - 2)); count++; } INSIST(isc_buffer_remaininglength(&source) == 0); result = dns_rdataset_next(rdataset); dns_rdata_reset(&rdata); } if (result != ISC_R_NOMORE) goto rollback; *countp = count; return (ISC_R_SUCCESS); rollback: INSIST(savedbuffer.used < 65536); dns_compress_rollback(cctx, (isc_uint16_t)savedbuffer.used); *countp = 0; *target = savedbuffer; return (result); } static void rdataset_disassociate(dns_rdataset_t *rdataset) { UNUSED(rdataset); } static isc_result_t rdataset_first(dns_rdataset_t *rdataset) { unsigned char *raw = rdataset->private3; unsigned int count; count = raw[0] * 256 + raw[1]; if (count == 0) { rdataset->private5 = NULL; return (ISC_R_NOMORE); } raw += 2; /* * The privateuint4 field is the number of rdata beyond the cursor * position, so we decrement the total count by one before storing * it. */ count--; rdataset->privateuint4 = count; rdataset->private5 = raw; return (ISC_R_SUCCESS); } static isc_result_t rdataset_next(dns_rdataset_t *rdataset) { unsigned int count; unsigned int length; unsigned char *raw; count = rdataset->privateuint4; if (count == 0) return (ISC_R_NOMORE); count--; rdataset->privateuint4 = count; raw = rdataset->private5; length = raw[0] * 256 + raw[1]; raw += length + 2; rdataset->private5 = raw; return (ISC_R_SUCCESS); } static void rdataset_current(dns_rdataset_t *rdataset, dns_rdata_t *rdata) { unsigned char *raw = rdataset->private5; isc_region_t r; REQUIRE(raw != NULL); r.length = raw[0] * 256 + raw[1]; raw += 2; r.base = raw; dns_rdata_fromregion(rdata, rdataset->rdclass, rdataset->type, &r); } static void rdataset_clone(dns_rdataset_t *source, dns_rdataset_t *target) { *target = *source; /* * Reset iterator state. */ target->privateuint4 = 0; target->private5 = NULL; } static unsigned int rdataset_count(dns_rdataset_t *rdataset) { unsigned char *raw = rdataset->private3; unsigned int count; count = raw[0] * 256 + raw[1]; return (count); } static void rdataset_settrust(dns_rdataset_t *rdataset, dns_trust_t trust) { unsigned char *raw = rdataset->private3; raw[-1] = (unsigned char)trust; } static dns_rdatasetmethods_t rdataset_methods = { rdataset_disassociate, rdataset_first, rdataset_next, rdataset_current, rdataset_clone, rdataset_count, NULL, NULL, NULL, NULL, NULL, NULL, NULL, rdataset_settrust, NULL }; isc_result_t dns_ncache_getrdataset(dns_rdataset_t *ncacherdataset, dns_name_t *name, dns_rdatatype_t type, dns_rdataset_t *rdataset) { isc_result_t result; dns_rdata_t rdata = DNS_RDATA_INIT; isc_region_t remaining; isc_buffer_t source; dns_name_t tname; dns_rdatatype_t ttype; dns_trust_t trust = dns_trust_none; dns_rdataset_t clone; REQUIRE(ncacherdataset != NULL); REQUIRE(ncacherdataset->type == 0); REQUIRE((ncacherdataset->attributes & DNS_RDATASETATTR_NEGATIVE) != 0); REQUIRE(name != NULL); REQUIRE(!dns_rdataset_isassociated(rdataset)); REQUIRE(type != dns_rdatatype_rrsig); dns_rdataset_init(&clone); dns_rdataset_clone(ncacherdataset, &clone); result = dns_rdataset_first(&clone); while (result == ISC_R_SUCCESS) { dns_rdataset_current(&clone, &rdata); isc_buffer_init(&source, rdata.data, rdata.length); isc_buffer_add(&source, rdata.length); dns_name_init(&tname, NULL); isc_buffer_remainingregion(&source, &remaining); dns_name_fromregion(&tname, &remaining); INSIST(remaining.length >= tname.length); isc_buffer_forward(&source, tname.length); remaining.length -= tname.length; INSIST(remaining.length >= 3); ttype = isc_buffer_getuint16(&source); if (ttype == type && dns_name_equal(&tname, name)) { trust = isc_buffer_getuint8(&source); INSIST(trust <= dns_trust_ultimate); isc_buffer_remainingregion(&source, &remaining); break; } result = dns_rdataset_next(&clone); dns_rdata_reset(&rdata); } dns_rdataset_disassociate(&clone); if (result == ISC_R_NOMORE) return (ISC_R_NOTFOUND); if (result != ISC_R_SUCCESS) return (result); INSIST(remaining.length != 0); rdataset->methods = &rdataset_methods; rdataset->rdclass = ncacherdataset->rdclass; rdataset->type = type; rdataset->covers = 0; rdataset->ttl = ncacherdataset->ttl; rdataset->trust = trust; rdataset->private1 = NULL; rdataset->private2 = NULL; rdataset->private3 = remaining.base; /* * Reset iterator state. */ rdataset->privateuint4 = 0; rdataset->private5 = NULL; rdataset->private6 = NULL; return (ISC_R_SUCCESS); } isc_result_t dns_ncache_getsigrdataset(dns_rdataset_t *ncacherdataset, dns_name_t *name, dns_rdatatype_t covers, dns_rdataset_t *rdataset) { dns_name_t tname; dns_rdata_rrsig_t rrsig; dns_rdata_t rdata = DNS_RDATA_INIT; dns_rdataset_t clone; dns_rdatatype_t type; dns_trust_t trust = dns_trust_none; isc_buffer_t source; isc_region_t remaining, sigregion; isc_result_t result; unsigned char *raw; unsigned int count; REQUIRE(ncacherdataset != NULL); REQUIRE(ncacherdataset->type == 0); REQUIRE((ncacherdataset->attributes & DNS_RDATASETATTR_NEGATIVE) != 0); REQUIRE(name != NULL); REQUIRE(!dns_rdataset_isassociated(rdataset)); dns_rdataset_init(&clone); dns_rdataset_clone(ncacherdataset, &clone); result = dns_rdataset_first(&clone); while (result == ISC_R_SUCCESS) { dns_rdataset_current(&clone, &rdata); isc_buffer_init(&source, rdata.data, rdata.length); isc_buffer_add(&source, rdata.length); dns_name_init(&tname, NULL); isc_buffer_remainingregion(&source, &remaining); dns_name_fromregion(&tname, &remaining); INSIST(remaining.length >= tname.length); isc_buffer_forward(&source, tname.length); - remaining.length -= tname.length; - remaining.base += tname.length; + isc_region_consume(&remaining, tname.length); INSIST(remaining.length >= 2); type = isc_buffer_getuint16(&source); - remaining.length -= 2; - remaining.base += 2; + isc_region_consume(&remaining, 2); if (type != dns_rdatatype_rrsig || !dns_name_equal(&tname, name)) { result = dns_rdataset_next(&clone); dns_rdata_reset(&rdata); continue; } INSIST(remaining.length >= 1); trust = isc_buffer_getuint8(&source); INSIST(trust <= dns_trust_ultimate); - remaining.length -= 1; - remaining.base += 1; + isc_region_consume(&remaining, 1); raw = remaining.base; count = raw[0] * 256 + raw[1]; INSIST(count > 0); raw += 2; sigregion.length = raw[0] * 256 + raw[1]; raw += 2; sigregion.base = raw; dns_rdata_reset(&rdata); dns_rdata_fromregion(&rdata, rdataset->rdclass, dns_rdatatype_rrsig, &sigregion); (void)dns_rdata_tostruct(&rdata, &rrsig, NULL); if (rrsig.covered == covers) { isc_buffer_remainingregion(&source, &remaining); break; } result = dns_rdataset_next(&clone); dns_rdata_reset(&rdata); } dns_rdataset_disassociate(&clone); if (result == ISC_R_NOMORE) return (ISC_R_NOTFOUND); if (result != ISC_R_SUCCESS) return (result); INSIST(remaining.length != 0); rdataset->methods = &rdataset_methods; rdataset->rdclass = ncacherdataset->rdclass; rdataset->type = dns_rdatatype_rrsig; rdataset->covers = covers; rdataset->ttl = ncacherdataset->ttl; rdataset->trust = trust; rdataset->private1 = NULL; rdataset->private2 = NULL; rdataset->private3 = remaining.base; /* * Reset iterator state. */ rdataset->privateuint4 = 0; rdataset->private5 = NULL; rdataset->private6 = NULL; return (ISC_R_SUCCESS); } void dns_ncache_current(dns_rdataset_t *ncacherdataset, dns_name_t *found, dns_rdataset_t *rdataset) { dns_rdata_t rdata = DNS_RDATA_INIT; dns_trust_t trust; isc_region_t remaining, sigregion; isc_buffer_t source; dns_name_t tname; dns_rdatatype_t type; unsigned int count; dns_rdata_rrsig_t rrsig; unsigned char *raw; REQUIRE(ncacherdataset != NULL); REQUIRE(ncacherdataset->type == 0); REQUIRE((ncacherdataset->attributes & DNS_RDATASETATTR_NEGATIVE) != 0); REQUIRE(found != NULL); REQUIRE(!dns_rdataset_isassociated(rdataset)); dns_rdataset_current(ncacherdataset, &rdata); isc_buffer_init(&source, rdata.data, rdata.length); isc_buffer_add(&source, rdata.length); dns_name_init(&tname, NULL); isc_buffer_remainingregion(&source, &remaining); dns_name_fromregion(found, &remaining); INSIST(remaining.length >= found->length); isc_buffer_forward(&source, found->length); remaining.length -= found->length; INSIST(remaining.length >= 5); type = isc_buffer_getuint16(&source); trust = isc_buffer_getuint8(&source); INSIST(trust <= dns_trust_ultimate); isc_buffer_remainingregion(&source, &remaining); rdataset->methods = &rdataset_methods; rdataset->rdclass = ncacherdataset->rdclass; rdataset->type = type; if (type == dns_rdatatype_rrsig) { /* * Extract covers from RRSIG. */ raw = remaining.base; count = raw[0] * 256 + raw[1]; INSIST(count > 0); raw += 2; sigregion.length = raw[0] * 256 + raw[1]; raw += 2; sigregion.base = raw; dns_rdata_reset(&rdata); dns_rdata_fromregion(&rdata, rdataset->rdclass, rdataset->type, &sigregion); (void)dns_rdata_tostruct(&rdata, &rrsig, NULL); rdataset->covers = rrsig.covered; } else rdataset->covers = 0; rdataset->ttl = ncacherdataset->ttl; rdataset->trust = trust; rdataset->private1 = NULL; rdataset->private2 = NULL; rdataset->private3 = remaining.base; /* * Reset iterator state. */ rdataset->privateuint4 = 0; rdataset->private5 = NULL; rdataset->private6 = NULL; } Index: stable/9/contrib/bind9/lib/dns/openssldh_link.c =================================================================== --- stable/9/contrib/bind9/lib/dns/openssldh_link.c (revision 287408) +++ stable/9/contrib/bind9/lib/dns/openssldh_link.c (revision 287409) @@ -1,678 +1,681 @@ /* * Portions Copyright (C) 2004-2009, 2011-2014 Internet Systems Consortium, Inc. ("ISC") * Portions Copyright (C) 1999-2002 Internet Software Consortium. * * Permission to use, copy, modify, and/or distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND ISC AND NETWORK ASSOCIATES DISCLAIMS * ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED * WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE * FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR * IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. * * Portions Copyright (C) 1995-2000 by Network Associates, Inc. * * Permission to use, copy, modify, and/or distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND ISC AND NETWORK ASSOCIATES DISCLAIMS * ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED * WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE * FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR * IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ /* * Principal Author: Brian Wellington * $Id: openssldh_link.c,v 1.20 2011/01/11 23:47:13 tbox Exp $ */ #ifdef OPENSSL #include #include #include #include #include #include #include "dst_internal.h" #include "dst_openssl.h" #include "dst_parse.h" #define PRIME768 "FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E088" \ "A67CC74020BBEA63B139B22514A08798E3404DDEF9519B3CD3A431B302B0A6DF25" \ "F14374FE1356D6D51C245E485B576625E7EC6F44C42E9A63A3620FFFFFFFFFFFFFFFF" #define PRIME1024 "FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E08" \ "8A67CC74020BBEA63B139B22514A08798E3404DDEF9519B3CD3A431B302B0A6DF2" \ "5F14374FE1356D6D51C245E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406" \ "B7EDEE386BFB5A899FA5AE9F24117C4B1FE649286651ECE65381FFFFFFFFFFFFFFFF" #define PRIME1536 "FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD1" \ "29024E088A67CC74020BBEA63B139B22514A08798E3404DD" \ "EF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245" \ "E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7ED" \ "EE386BFB5A899FA5AE9F24117C4B1FE649286651ECE45B3D" \ "C2007CB8A163BF0598DA48361C55D39A69163FA8FD24CF5F" \ "83655D23DCA3AD961C62F356208552BB9ED529077096966D" \ "670C354E4ABC9804F1746C08CA237327FFFFFFFFFFFFFFFF" static isc_result_t openssldh_todns(const dst_key_t *key, isc_buffer_t *data); static BIGNUM bn2, bn768, bn1024, bn1536; static isc_result_t openssldh_computesecret(const dst_key_t *pub, const dst_key_t *priv, isc_buffer_t *secret) { DH *dhpub, *dhpriv; int ret; isc_region_t r; unsigned int len; REQUIRE(pub->keydata.dh != NULL); REQUIRE(priv->keydata.dh != NULL); dhpub = pub->keydata.dh; dhpriv = priv->keydata.dh; len = DH_size(dhpriv); isc_buffer_availableregion(secret, &r); if (r.length < len) return (ISC_R_NOSPACE); ret = DH_compute_key(r.base, dhpub->pub_key, dhpriv); if (ret <= 0) return (dst__openssl_toresult2("DH_compute_key", DST_R_COMPUTESECRETFAILURE)); isc_buffer_add(secret, len); return (ISC_R_SUCCESS); } static isc_boolean_t openssldh_compare(const dst_key_t *key1, const dst_key_t *key2) { int status; DH *dh1, *dh2; dh1 = key1->keydata.dh; dh2 = key2->keydata.dh; if (dh1 == NULL && dh2 == NULL) return (ISC_TRUE); else if (dh1 == NULL || dh2 == NULL) return (ISC_FALSE); status = BN_cmp(dh1->p, dh2->p) || BN_cmp(dh1->g, dh2->g) || BN_cmp(dh1->pub_key, dh2->pub_key); if (status != 0) return (ISC_FALSE); if (dh1->priv_key != NULL || dh2->priv_key != NULL) { if (dh1->priv_key == NULL || dh2->priv_key == NULL) return (ISC_FALSE); if (BN_cmp(dh1->priv_key, dh2->priv_key) != 0) return (ISC_FALSE); } return (ISC_TRUE); } static isc_boolean_t openssldh_paramcompare(const dst_key_t *key1, const dst_key_t *key2) { int status; DH *dh1, *dh2; dh1 = key1->keydata.dh; dh2 = key2->keydata.dh; if (dh1 == NULL && dh2 == NULL) return (ISC_TRUE); else if (dh1 == NULL || dh2 == NULL) return (ISC_FALSE); status = BN_cmp(dh1->p, dh2->p) || BN_cmp(dh1->g, dh2->g); if (status != 0) return (ISC_FALSE); return (ISC_TRUE); } #if OPENSSL_VERSION_NUMBER > 0x00908000L static int progress_cb(int p, int n, BN_GENCB *cb) { union { void *dptr; void (*fptr)(int); } u; UNUSED(n); u.dptr = cb->arg; if (u.fptr != NULL) u.fptr(p); return (1); } #endif static isc_result_t openssldh_generate(dst_key_t *key, int generator, void (*callback)(int)) { DH *dh = NULL; #if OPENSSL_VERSION_NUMBER > 0x00908000L BN_GENCB cb; union { void *dptr; void (*fptr)(int); } u; #else UNUSED(callback); #endif if (generator == 0) { if (key->key_size == 768 || key->key_size == 1024 || key->key_size == 1536) { dh = DH_new(); if (dh == NULL) return (dst__openssl_toresult(ISC_R_NOMEMORY)); if (key->key_size == 768) dh->p = &bn768; else if (key->key_size == 1024) dh->p = &bn1024; else dh->p = &bn1536; dh->g = &bn2; } else generator = 2; } if (generator != 0) { #if OPENSSL_VERSION_NUMBER > 0x00908000L dh = DH_new(); if (dh == NULL) return (dst__openssl_toresult(ISC_R_NOMEMORY)); if (callback == NULL) { BN_GENCB_set_old(&cb, NULL, NULL); } else { u.fptr = callback; BN_GENCB_set(&cb, &progress_cb, u.dptr); } if (!DH_generate_parameters_ex(dh, key->key_size, generator, &cb)) { DH_free(dh); return (dst__openssl_toresult2( "DH_generate_parameters_ex", DST_R_OPENSSLFAILURE)); } #else dh = DH_generate_parameters(key->key_size, generator, NULL, NULL); #endif } if (dh == NULL) return (dst__openssl_toresult2("DH_generate_parameters", DST_R_OPENSSLFAILURE)); if (DH_generate_key(dh) == 0) { DH_free(dh); return (dst__openssl_toresult2("DH_generate_key", DST_R_OPENSSLFAILURE)); } dh->flags &= ~DH_FLAG_CACHE_MONT_P; key->keydata.dh = dh; return (ISC_R_SUCCESS); } static isc_boolean_t openssldh_isprivate(const dst_key_t *key) { DH *dh = key->keydata.dh; return (ISC_TF(dh != NULL && dh->priv_key != NULL)); } static void openssldh_destroy(dst_key_t *key) { DH *dh = key->keydata.dh; if (dh == NULL) return; if (dh->p == &bn768 || dh->p == &bn1024 || dh->p == &bn1536) dh->p = NULL; if (dh->g == &bn2) dh->g = NULL; DH_free(dh); key->keydata.dh = NULL; } static void uint16_toregion(isc_uint16_t val, isc_region_t *region) { - *region->base++ = (val & 0xff00) >> 8; - *region->base++ = (val & 0x00ff); + *region->base = (val & 0xff00) >> 8; + isc_region_consume(region, 1); + *region->base = (val & 0x00ff); + isc_region_consume(region, 1); } static isc_uint16_t uint16_fromregion(isc_region_t *region) { isc_uint16_t val; unsigned char *cp = region->base; val = ((unsigned int)(cp[0])) << 8; val |= ((unsigned int)(cp[1])); - region->base += 2; + isc_region_consume(region, 2); + return (val); } static isc_result_t openssldh_todns(const dst_key_t *key, isc_buffer_t *data) { DH *dh; isc_region_t r; isc_uint16_t dnslen, plen, glen, publen; REQUIRE(key->keydata.dh != NULL); dh = key->keydata.dh; isc_buffer_availableregion(data, &r); if (dh->g == &bn2 && (dh->p == &bn768 || dh->p == &bn1024 || dh->p == &bn1536)) { plen = 1; glen = 0; } else { plen = BN_num_bytes(dh->p); glen = BN_num_bytes(dh->g); } publen = BN_num_bytes(dh->pub_key); dnslen = plen + glen + publen + 6; if (r.length < (unsigned int) dnslen) return (ISC_R_NOSPACE); uint16_toregion(plen, &r); if (plen == 1) { if (dh->p == &bn768) *r.base = 1; else if (dh->p == &bn1024) *r.base = 2; else *r.base = 3; } else BN_bn2bin(dh->p, r.base); - r.base += plen; + isc_region_consume(&r, plen); uint16_toregion(glen, &r); if (glen > 0) BN_bn2bin(dh->g, r.base); - r.base += glen; + isc_region_consume(&r, glen); uint16_toregion(publen, &r); BN_bn2bin(dh->pub_key, r.base); - r.base += publen; + isc_region_consume(&r, publen); isc_buffer_add(data, dnslen); return (ISC_R_SUCCESS); } static isc_result_t openssldh_fromdns(dst_key_t *key, isc_buffer_t *data) { DH *dh; isc_region_t r; isc_uint16_t plen, glen, publen; int special = 0; isc_buffer_remainingregion(data, &r); if (r.length == 0) return (ISC_R_SUCCESS); dh = DH_new(); if (dh == NULL) return (dst__openssl_toresult(ISC_R_NOMEMORY)); dh->flags &= ~DH_FLAG_CACHE_MONT_P; /* * Read the prime length. 1 & 2 are table entries, > 16 means a * prime follows, otherwise an error. */ if (r.length < 2) { DH_free(dh); return (DST_R_INVALIDPUBLICKEY); } plen = uint16_fromregion(&r); if (plen < 16 && plen != 1 && plen != 2) { DH_free(dh); return (DST_R_INVALIDPUBLICKEY); } if (r.length < plen) { DH_free(dh); return (DST_R_INVALIDPUBLICKEY); } if (plen == 1 || plen == 2) { - if (plen == 1) - special = *r.base++; - else + if (plen == 1) { + special = *r.base; + isc_region_consume(&r, 1); + } else { special = uint16_fromregion(&r); + } switch (special) { case 1: dh->p = &bn768; break; case 2: dh->p = &bn1024; break; case 3: dh->p = &bn1536; break; default: DH_free(dh); return (DST_R_INVALIDPUBLICKEY); } - } - else { + } else { dh->p = BN_bin2bn(r.base, plen, NULL); - r.base += plen; + isc_region_consume(&r, plen); } /* * Read the generator length. This should be 0 if the prime was * special, but it might not be. If it's 0 and the prime is not * special, we have a problem. */ if (r.length < 2) { DH_free(dh); return (DST_R_INVALIDPUBLICKEY); } glen = uint16_fromregion(&r); if (r.length < glen) { DH_free(dh); return (DST_R_INVALIDPUBLICKEY); } if (special != 0) { if (glen == 0) dh->g = &bn2; else { dh->g = BN_bin2bn(r.base, glen, NULL); if (BN_cmp(dh->g, &bn2) == 0) { BN_free(dh->g); dh->g = &bn2; } else { DH_free(dh); return (DST_R_INVALIDPUBLICKEY); } } - } - else { + } else { if (glen == 0) { DH_free(dh); return (DST_R_INVALIDPUBLICKEY); } dh->g = BN_bin2bn(r.base, glen, NULL); } - r.base += glen; + isc_region_consume(&r, glen); if (r.length < 2) { DH_free(dh); return (DST_R_INVALIDPUBLICKEY); } publen = uint16_fromregion(&r); if (r.length < publen) { DH_free(dh); return (DST_R_INVALIDPUBLICKEY); } dh->pub_key = BN_bin2bn(r.base, publen, NULL); - r.base += publen; + isc_region_consume(&r, publen); key->key_size = BN_num_bits(dh->p); isc_buffer_forward(data, plen + glen + publen + 6); key->keydata.dh = dh; return (ISC_R_SUCCESS); } static isc_result_t openssldh_tofile(const dst_key_t *key, const char *directory) { int i; DH *dh; dst_private_t priv; unsigned char *bufs[4]; isc_result_t result; if (key->keydata.dh == NULL) return (DST_R_NULLKEY); dh = key->keydata.dh; memset(bufs, 0, sizeof(bufs)); for (i = 0; i < 4; i++) { bufs[i] = isc_mem_get(key->mctx, BN_num_bytes(dh->p)); if (bufs[i] == NULL) { result = ISC_R_NOMEMORY; goto fail; } } i = 0; priv.elements[i].tag = TAG_DH_PRIME; priv.elements[i].length = BN_num_bytes(dh->p); BN_bn2bin(dh->p, bufs[i]); priv.elements[i].data = bufs[i]; i++; priv.elements[i].tag = TAG_DH_GENERATOR; priv.elements[i].length = BN_num_bytes(dh->g); BN_bn2bin(dh->g, bufs[i]); priv.elements[i].data = bufs[i]; i++; priv.elements[i].tag = TAG_DH_PRIVATE; priv.elements[i].length = BN_num_bytes(dh->priv_key); BN_bn2bin(dh->priv_key, bufs[i]); priv.elements[i].data = bufs[i]; i++; priv.elements[i].tag = TAG_DH_PUBLIC; priv.elements[i].length = BN_num_bytes(dh->pub_key); BN_bn2bin(dh->pub_key, bufs[i]); priv.elements[i].data = bufs[i]; i++; priv.nelements = i; result = dst__privstruct_writefile(key, &priv, directory); fail: for (i = 0; i < 4; i++) { if (bufs[i] == NULL) break; isc_mem_put(key->mctx, bufs[i], BN_num_bytes(dh->p)); } return (result); } static isc_result_t openssldh_parse(dst_key_t *key, isc_lex_t *lexer, dst_key_t *pub) { dst_private_t priv; isc_result_t ret; int i; DH *dh = NULL; isc_mem_t *mctx; #define DST_RET(a) {ret = a; goto err;} UNUSED(pub); mctx = key->mctx; /* read private key file */ ret = dst__privstruct_parse(key, DST_ALG_DH, lexer, mctx, &priv); if (ret != ISC_R_SUCCESS) return (ret); dh = DH_new(); if (dh == NULL) DST_RET(ISC_R_NOMEMORY); dh->flags &= ~DH_FLAG_CACHE_MONT_P; key->keydata.dh = dh; for (i = 0; i < priv.nelements; i++) { BIGNUM *bn; bn = BN_bin2bn(priv.elements[i].data, priv.elements[i].length, NULL); if (bn == NULL) DST_RET(ISC_R_NOMEMORY); switch (priv.elements[i].tag) { case TAG_DH_PRIME: dh->p = bn; break; case TAG_DH_GENERATOR: dh->g = bn; break; case TAG_DH_PRIVATE: dh->priv_key = bn; break; case TAG_DH_PUBLIC: dh->pub_key = bn; break; } } dst__privstruct_free(&priv, mctx); key->key_size = BN_num_bits(dh->p); if ((key->key_size == 768 || key->key_size == 1024 || key->key_size == 1536) && BN_cmp(dh->g, &bn2) == 0) { if (key->key_size == 768 && BN_cmp(dh->p, &bn768) == 0) { BN_free(dh->p); BN_free(dh->g); dh->p = &bn768; dh->g = &bn2; } else if (key->key_size == 1024 && BN_cmp(dh->p, &bn1024) == 0) { BN_free(dh->p); BN_free(dh->g); dh->p = &bn1024; dh->g = &bn2; } else if (key->key_size == 1536 && BN_cmp(dh->p, &bn1536) == 0) { BN_free(dh->p); BN_free(dh->g); dh->p = &bn1536; dh->g = &bn2; } } return (ISC_R_SUCCESS); err: openssldh_destroy(key); dst__privstruct_free(&priv, mctx); memset(&priv, 0, sizeof(priv)); return (ret); } static void BN_fromhex(BIGNUM *b, const char *str) { static const char hexdigits[] = "0123456789abcdef"; unsigned char data[512]; unsigned int i; BIGNUM *out; RUNTIME_CHECK(strlen(str) < 1024U && strlen(str) % 2 == 0U); for (i = 0; i < strlen(str); i += 2) { char *s; unsigned int high, low; s = strchr(hexdigits, tolower((unsigned char)str[i])); RUNTIME_CHECK(s != NULL); high = (unsigned int)(s - hexdigits); s = strchr(hexdigits, tolower((unsigned char)str[i + 1])); RUNTIME_CHECK(s != NULL); low = (unsigned int)(s - hexdigits); data[i/2] = (unsigned char)((high << 4) + low); } out = BN_bin2bn(data, strlen(str)/2, b); RUNTIME_CHECK(out != NULL); } static void openssldh_cleanup(void) { BN_free(&bn2); BN_free(&bn768); BN_free(&bn1024); BN_free(&bn1536); } static dst_func_t openssldh_functions = { NULL, /*%< createctx */ NULL, /*%< destroyctx */ NULL, /*%< adddata */ NULL, /*%< openssldh_sign */ NULL, /*%< openssldh_verify */ NULL, /*%< openssldh_verify2 */ openssldh_computesecret, openssldh_compare, openssldh_paramcompare, openssldh_generate, openssldh_isprivate, openssldh_destroy, openssldh_todns, openssldh_fromdns, openssldh_tofile, openssldh_parse, openssldh_cleanup, NULL, /*%< fromlabel */ NULL, /*%< dump */ NULL, /*%< restore */ }; isc_result_t dst__openssldh_init(dst_func_t **funcp) { REQUIRE(funcp != NULL); if (*funcp == NULL) { BN_init(&bn2); BN_init(&bn768); BN_init(&bn1024); BN_init(&bn1536); BN_set_word(&bn2, 2); BN_fromhex(&bn768, PRIME768); BN_fromhex(&bn1024, PRIME1024); BN_fromhex(&bn1536, PRIME1536); *funcp = &openssldh_functions; } return (ISC_R_SUCCESS); } #else /* OPENSSL */ #include EMPTY_TRANSLATION_UNIT #endif /* OPENSSL */ /*! \file */ Index: stable/9/contrib/bind9/lib/dns/openssldsa_link.c =================================================================== --- stable/9/contrib/bind9/lib/dns/openssldsa_link.c (revision 287408) +++ stable/9/contrib/bind9/lib/dns/openssldsa_link.c (revision 287409) @@ -1,678 +1,686 @@ /* * Portions Copyright (C) 2004-2009, 2011-2013 Internet Systems Consortium, Inc. ("ISC") * Portions Copyright (C) 1999-2002 Internet Software Consortium. * * Permission to use, copy, modify, and/or distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND ISC AND NETWORK ASSOCIATES DISCLAIMS * ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED * WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE * FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR * IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. * * Portions Copyright (C) 1995-2000 by Network Associates, Inc. * * Permission to use, copy, modify, and/or distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND ISC AND NETWORK ASSOCIATES DISCLAIMS * ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED * WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE * FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR * IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ -/* $Id$ */ - #ifdef OPENSSL #ifndef USE_EVP #define USE_EVP 1 #endif #include #include #include #include #include #include #include #include "dst_internal.h" #include "dst_openssl.h" #include "dst_parse.h" #include static isc_result_t openssldsa_todns(const dst_key_t *key, isc_buffer_t *data); static isc_result_t openssldsa_createctx(dst_key_t *key, dst_context_t *dctx) { #if USE_EVP EVP_MD_CTX *evp_md_ctx; UNUSED(key); evp_md_ctx = EVP_MD_CTX_create(); if (evp_md_ctx == NULL) return (ISC_R_NOMEMORY); if (!EVP_DigestInit_ex(evp_md_ctx, EVP_dss1(), NULL)) { EVP_MD_CTX_destroy(evp_md_ctx); return (ISC_R_FAILURE); } dctx->ctxdata.evp_md_ctx = evp_md_ctx; return (ISC_R_SUCCESS); #else isc_sha1_t *sha1ctx; UNUSED(key); sha1ctx = isc_mem_get(dctx->mctx, sizeof(isc_sha1_t)); isc_sha1_init(sha1ctx); dctx->ctxdata.sha1ctx = sha1ctx; return (ISC_R_SUCCESS); #endif } static void openssldsa_destroyctx(dst_context_t *dctx) { #if USE_EVP EVP_MD_CTX *evp_md_ctx = dctx->ctxdata.evp_md_ctx; if (evp_md_ctx != NULL) { EVP_MD_CTX_destroy(evp_md_ctx); dctx->ctxdata.evp_md_ctx = NULL; } #else isc_sha1_t *sha1ctx = dctx->ctxdata.sha1ctx; if (sha1ctx != NULL) { isc_sha1_invalidate(sha1ctx); isc_mem_put(dctx->mctx, sha1ctx, sizeof(isc_sha1_t)); dctx->ctxdata.sha1ctx = NULL; } #endif } static isc_result_t openssldsa_adddata(dst_context_t *dctx, const isc_region_t *data) { #if USE_EVP EVP_MD_CTX *evp_md_ctx = dctx->ctxdata.evp_md_ctx; if (!EVP_DigestUpdate(evp_md_ctx, data->base, data->length)) { return (ISC_R_FAILURE); } #else isc_sha1_t *sha1ctx = dctx->ctxdata.sha1ctx; isc_sha1_update(sha1ctx, data->base, data->length); #endif return (ISC_R_SUCCESS); } static int BN_bn2bin_fixed(BIGNUM *bn, unsigned char *buf, int size) { int bytes = size - BN_num_bytes(bn); while (bytes-- > 0) *buf++ = 0; BN_bn2bin(bn, buf); return (size); } static isc_result_t openssldsa_sign(dst_context_t *dctx, isc_buffer_t *sig) { dst_key_t *key = dctx->key; DSA *dsa = key->keydata.dsa; isc_region_t r; DSA_SIG *dsasig; + unsigned int klen; #if USE_EVP EVP_MD_CTX *evp_md_ctx = dctx->ctxdata.evp_md_ctx; EVP_PKEY *pkey; unsigned char *sigbuf; const unsigned char *sb; unsigned int siglen; #else isc_sha1_t *sha1ctx = dctx->ctxdata.sha1ctx; unsigned char digest[ISC_SHA1_DIGESTLENGTH]; #endif isc_buffer_availableregion(sig, &r); if (r.length < ISC_SHA1_DIGESTLENGTH * 2 + 1) return (ISC_R_NOSPACE); #if USE_EVP pkey = EVP_PKEY_new(); if (pkey == NULL) return (ISC_R_NOMEMORY); if (!EVP_PKEY_set1_DSA(pkey, dsa)) { EVP_PKEY_free(pkey); return (ISC_R_FAILURE); } sigbuf = malloc(EVP_PKEY_size(pkey)); if (sigbuf == NULL) { EVP_PKEY_free(pkey); return (ISC_R_NOMEMORY); } if (!EVP_SignFinal(evp_md_ctx, sigbuf, &siglen, pkey)) { EVP_PKEY_free(pkey); free(sigbuf); return (dst__openssl_toresult3(dctx->category, "EVP_SignFinal", ISC_R_FAILURE)); } INSIST(EVP_PKEY_size(pkey) >= (int) siglen); EVP_PKEY_free(pkey); /* Convert from Dss-Sig-Value (RFC2459). */ dsasig = DSA_SIG_new(); if (dsasig == NULL) { free(sigbuf); return (ISC_R_NOMEMORY); } sb = sigbuf; if (d2i_DSA_SIG(&dsasig, &sb, (long) siglen) == NULL) { free(sigbuf); return (dst__openssl_toresult3(dctx->category, "d2i_DSA_SIG", ISC_R_FAILURE)); } free(sigbuf); + #elif 0 /* Only use EVP for the Digest */ if (!EVP_DigestFinal_ex(evp_md_ctx, digest, &siglen)) { return (dst__openssl_toresult3(dctx->category, "EVP_DigestFinal_ex", ISC_R_FAILURE)); } dsasig = DSA_do_sign(digest, ISC_SHA1_DIGESTLENGTH, dsa); if (dsasig == NULL) return (dst__openssl_toresult3(dctx->category, "DSA_do_sign", DST_R_SIGNFAILURE)); #else isc_sha1_final(sha1ctx, digest); dsasig = DSA_do_sign(digest, ISC_SHA1_DIGESTLENGTH, dsa); if (dsasig == NULL) return (dst__openssl_toresult3(dctx->category, "DSA_do_sign", DST_R_SIGNFAILURE)); #endif - *r.base++ = (key->key_size - 512)/64; + + klen = (key->key_size - 512)/64; + if (klen > 255) + return (ISC_R_FAILURE); + *r.base = klen; + isc_region_consume(&r, 1); + BN_bn2bin_fixed(dsasig->r, r.base, ISC_SHA1_DIGESTLENGTH); - r.base += ISC_SHA1_DIGESTLENGTH; + isc_region_consume(&r, ISC_SHA1_DIGESTLENGTH); BN_bn2bin_fixed(dsasig->s, r.base, ISC_SHA1_DIGESTLENGTH); - r.base += ISC_SHA1_DIGESTLENGTH; + isc_region_consume(&r, ISC_SHA1_DIGESTLENGTH); DSA_SIG_free(dsasig); isc_buffer_add(sig, ISC_SHA1_DIGESTLENGTH * 2 + 1); return (ISC_R_SUCCESS); } static isc_result_t openssldsa_verify(dst_context_t *dctx, const isc_region_t *sig) { dst_key_t *key = dctx->key; DSA *dsa = key->keydata.dsa; int status = 0; unsigned char *cp = sig->base; DSA_SIG *dsasig; #if USE_EVP EVP_MD_CTX *evp_md_ctx = dctx->ctxdata.evp_md_ctx; #if 0 EVP_PKEY *pkey; unsigned char *sigbuf; #endif unsigned int siglen; #else isc_sha1_t *sha1ctx = dctx->ctxdata.sha1ctx; #endif unsigned char digest[ISC_SHA1_DIGESTLENGTH]; #if USE_EVP #if 1 /* Only use EVP for the digest */ if (!EVP_DigestFinal_ex(evp_md_ctx, digest, &siglen)) { return (ISC_R_FAILURE); } #endif #else isc_sha1_final(sha1ctx, digest); #endif if (sig->length != 2 * ISC_SHA1_DIGESTLENGTH + 1) { return (DST_R_VERIFYFAILURE); } cp++; /*%< Skip T */ dsasig = DSA_SIG_new(); if (dsasig == NULL) return (ISC_R_NOMEMORY); dsasig->r = BN_bin2bn(cp, ISC_SHA1_DIGESTLENGTH, NULL); cp += ISC_SHA1_DIGESTLENGTH; dsasig->s = BN_bin2bn(cp, ISC_SHA1_DIGESTLENGTH, NULL); #if 0 pkey = EVP_PKEY_new(); if (pkey == NULL) return (ISC_R_NOMEMORY); if (!EVP_PKEY_set1_DSA(pkey, dsa)) { EVP_PKEY_free(pkey); return (ISC_R_FAILURE); } /* Convert to Dss-Sig-Value (RFC2459). */ sigbuf = malloc(EVP_PKEY_size(pkey) + 50); if (sigbuf == NULL) { EVP_PKEY_free(pkey); return (ISC_R_NOMEMORY); } siglen = (unsigned) i2d_DSA_SIG(dsasig, &sigbuf); INSIST(EVP_PKEY_size(pkey) >= (int) siglen); status = EVP_VerifyFinal(evp_md_ctx, sigbuf, siglen, pkey); EVP_PKEY_free(pkey); free(sigbuf); #else status = DSA_do_verify(digest, ISC_SHA1_DIGESTLENGTH, dsasig, dsa); #endif DSA_SIG_free(dsasig); switch (status) { case 1: return (ISC_R_SUCCESS); case 0: return (dst__openssl_toresult(DST_R_VERIFYFAILURE)); default: return (dst__openssl_toresult3(dctx->category, "DSA_do_verify", DST_R_VERIFYFAILURE)); } } static isc_boolean_t openssldsa_compare(const dst_key_t *key1, const dst_key_t *key2) { int status; DSA *dsa1, *dsa2; dsa1 = key1->keydata.dsa; dsa2 = key2->keydata.dsa; if (dsa1 == NULL && dsa2 == NULL) return (ISC_TRUE); else if (dsa1 == NULL || dsa2 == NULL) return (ISC_FALSE); status = BN_cmp(dsa1->p, dsa2->p) || BN_cmp(dsa1->q, dsa2->q) || BN_cmp(dsa1->g, dsa2->g) || BN_cmp(dsa1->pub_key, dsa2->pub_key); if (status != 0) return (ISC_FALSE); if (dsa1->priv_key != NULL || dsa2->priv_key != NULL) { if (dsa1->priv_key == NULL || dsa2->priv_key == NULL) return (ISC_FALSE); if (BN_cmp(dsa1->priv_key, dsa2->priv_key)) return (ISC_FALSE); } return (ISC_TRUE); } #if OPENSSL_VERSION_NUMBER > 0x00908000L static int progress_cb(int p, int n, BN_GENCB *cb) { union { void *dptr; void (*fptr)(int); } u; UNUSED(n); u.dptr = cb->arg; if (u.fptr != NULL) u.fptr(p); return (1); } #endif static isc_result_t openssldsa_generate(dst_key_t *key, int unused, void (*callback)(int)) { DSA *dsa; unsigned char rand_array[ISC_SHA1_DIGESTLENGTH]; isc_result_t result; #if OPENSSL_VERSION_NUMBER > 0x00908000L BN_GENCB cb; union { void *dptr; void (*fptr)(int); } u; #else UNUSED(callback); #endif UNUSED(unused); result = dst__entropy_getdata(rand_array, sizeof(rand_array), ISC_FALSE); if (result != ISC_R_SUCCESS) return (result); #if OPENSSL_VERSION_NUMBER > 0x00908000L dsa = DSA_new(); if (dsa == NULL) return (dst__openssl_toresult(DST_R_OPENSSLFAILURE)); if (callback == NULL) { BN_GENCB_set_old(&cb, NULL, NULL); } else { u.fptr = callback; BN_GENCB_set(&cb, &progress_cb, u.dptr); } if (!DSA_generate_parameters_ex(dsa, key->key_size, rand_array, ISC_SHA1_DIGESTLENGTH, NULL, NULL, &cb)) { DSA_free(dsa); return (dst__openssl_toresult2("DSA_generate_parameters_ex", DST_R_OPENSSLFAILURE)); } #else dsa = DSA_generate_parameters(key->key_size, rand_array, ISC_SHA1_DIGESTLENGTH, NULL, NULL, NULL, NULL); if (dsa == NULL) return (dst__openssl_toresult2("DSA_generate_parameters", DST_R_OPENSSLFAILURE)); #endif if (DSA_generate_key(dsa) == 0) { DSA_free(dsa); return (dst__openssl_toresult2("DSA_generate_key", DST_R_OPENSSLFAILURE)); } dsa->flags &= ~DSA_FLAG_CACHE_MONT_P; key->keydata.dsa = dsa; return (ISC_R_SUCCESS); } static isc_boolean_t openssldsa_isprivate(const dst_key_t *key) { DSA *dsa = key->keydata.dsa; return (ISC_TF(dsa != NULL && dsa->priv_key != NULL)); } static void openssldsa_destroy(dst_key_t *key) { DSA *dsa = key->keydata.dsa; DSA_free(dsa); key->keydata.dsa = NULL; } static isc_result_t openssldsa_todns(const dst_key_t *key, isc_buffer_t *data) { DSA *dsa; isc_region_t r; int dnslen; unsigned int t, p_bytes; REQUIRE(key->keydata.dsa != NULL); dsa = key->keydata.dsa; isc_buffer_availableregion(data, &r); t = (BN_num_bytes(dsa->p) - 64) / 8; if (t > 8) return (DST_R_INVALIDPUBLICKEY); p_bytes = 64 + 8 * t; dnslen = 1 + (key->key_size * 3)/8 + ISC_SHA1_DIGESTLENGTH; if (r.length < (unsigned int) dnslen) return (ISC_R_NOSPACE); - *r.base++ = t; + *r.base = t; + isc_region_consume(&r, 1); BN_bn2bin_fixed(dsa->q, r.base, ISC_SHA1_DIGESTLENGTH); - r.base += ISC_SHA1_DIGESTLENGTH; + isc_region_consume(&r, ISC_SHA1_DIGESTLENGTH); BN_bn2bin_fixed(dsa->p, r.base, key->key_size/8); - r.base += p_bytes; + isc_region_consume(&r, p_bytes); BN_bn2bin_fixed(dsa->g, r.base, key->key_size/8); - r.base += p_bytes; + isc_region_consume(&r, p_bytes); BN_bn2bin_fixed(dsa->pub_key, r.base, key->key_size/8); - r.base += p_bytes; + isc_region_consume(&r, p_bytes); isc_buffer_add(data, dnslen); return (ISC_R_SUCCESS); } static isc_result_t openssldsa_fromdns(dst_key_t *key, isc_buffer_t *data) { DSA *dsa; isc_region_t r; unsigned int t, p_bytes; isc_mem_t *mctx = key->mctx; UNUSED(mctx); isc_buffer_remainingregion(data, &r); if (r.length == 0) return (ISC_R_SUCCESS); dsa = DSA_new(); if (dsa == NULL) return (ISC_R_NOMEMORY); dsa->flags &= ~DSA_FLAG_CACHE_MONT_P; - t = (unsigned int) *r.base++; + t = (unsigned int) *r.base; + isc_region_consume(&r, 1); if (t > 8) { DSA_free(dsa); return (DST_R_INVALIDPUBLICKEY); } p_bytes = 64 + 8 * t; - if (r.length < 1 + ISC_SHA1_DIGESTLENGTH + 3 * p_bytes) { + if (r.length < ISC_SHA1_DIGESTLENGTH + 3 * p_bytes) { DSA_free(dsa); return (DST_R_INVALIDPUBLICKEY); } dsa->q = BN_bin2bn(r.base, ISC_SHA1_DIGESTLENGTH, NULL); - r.base += ISC_SHA1_DIGESTLENGTH; + isc_region_consume(&r, ISC_SHA1_DIGESTLENGTH); dsa->p = BN_bin2bn(r.base, p_bytes, NULL); - r.base += p_bytes; + isc_region_consume(&r, p_bytes); dsa->g = BN_bin2bn(r.base, p_bytes, NULL); - r.base += p_bytes; + isc_region_consume(&r, p_bytes); dsa->pub_key = BN_bin2bn(r.base, p_bytes, NULL); - r.base += p_bytes; + isc_region_consume(&r, p_bytes); key->key_size = p_bytes * 8; isc_buffer_forward(data, 1 + ISC_SHA1_DIGESTLENGTH + 3 * p_bytes); key->keydata.dsa = dsa; return (ISC_R_SUCCESS); } static isc_result_t openssldsa_tofile(const dst_key_t *key, const char *directory) { int cnt = 0; DSA *dsa; dst_private_t priv; unsigned char bufs[5][128]; if (key->keydata.dsa == NULL) return (DST_R_NULLKEY); if (key->external) { priv.nelements = 0; return (dst__privstruct_writefile(key, &priv, directory)); } dsa = key->keydata.dsa; priv.elements[cnt].tag = TAG_DSA_PRIME; priv.elements[cnt].length = BN_num_bytes(dsa->p); BN_bn2bin(dsa->p, bufs[cnt]); priv.elements[cnt].data = bufs[cnt]; cnt++; priv.elements[cnt].tag = TAG_DSA_SUBPRIME; priv.elements[cnt].length = BN_num_bytes(dsa->q); BN_bn2bin(dsa->q, bufs[cnt]); priv.elements[cnt].data = bufs[cnt]; cnt++; priv.elements[cnt].tag = TAG_DSA_BASE; priv.elements[cnt].length = BN_num_bytes(dsa->g); BN_bn2bin(dsa->g, bufs[cnt]); priv.elements[cnt].data = bufs[cnt]; cnt++; priv.elements[cnt].tag = TAG_DSA_PRIVATE; priv.elements[cnt].length = BN_num_bytes(dsa->priv_key); BN_bn2bin(dsa->priv_key, bufs[cnt]); priv.elements[cnt].data = bufs[cnt]; cnt++; priv.elements[cnt].tag = TAG_DSA_PUBLIC; priv.elements[cnt].length = BN_num_bytes(dsa->pub_key); BN_bn2bin(dsa->pub_key, bufs[cnt]); priv.elements[cnt].data = bufs[cnt]; cnt++; priv.nelements = cnt; return (dst__privstruct_writefile(key, &priv, directory)); } static isc_result_t openssldsa_parse(dst_key_t *key, isc_lex_t *lexer, dst_key_t *pub) { dst_private_t priv; isc_result_t ret; int i; DSA *dsa = NULL; isc_mem_t *mctx = key->mctx; #define DST_RET(a) {ret = a; goto err;} UNUSED(pub); /* read private key file */ ret = dst__privstruct_parse(key, DST_ALG_DSA, lexer, mctx, &priv); if (ret != ISC_R_SUCCESS) return (ret); dsa = DSA_new(); if (dsa == NULL) DST_RET(ISC_R_NOMEMORY); dsa->flags &= ~DSA_FLAG_CACHE_MONT_P; key->keydata.dsa = dsa; for (i=0; i < priv.nelements; i++) { BIGNUM *bn; bn = BN_bin2bn(priv.elements[i].data, priv.elements[i].length, NULL); if (bn == NULL) DST_RET(ISC_R_NOMEMORY); switch (priv.elements[i].tag) { case TAG_DSA_PRIME: dsa->p = bn; break; case TAG_DSA_SUBPRIME: dsa->q = bn; break; case TAG_DSA_BASE: dsa->g = bn; break; case TAG_DSA_PRIVATE: dsa->priv_key = bn; break; case TAG_DSA_PUBLIC: dsa->pub_key = bn; break; } } dst__privstruct_free(&priv, mctx); if (key->external) { if (pub == NULL) DST_RET(DST_R_INVALIDPRIVATEKEY); dsa->q = pub->keydata.dsa->q; pub->keydata.dsa->q = NULL; dsa->p = pub->keydata.dsa->p; pub->keydata.dsa->p = NULL; dsa->g = pub->keydata.dsa->g; pub->keydata.dsa->g = NULL; dsa->pub_key = pub->keydata.dsa->pub_key; pub->keydata.dsa->pub_key = NULL; } key->key_size = BN_num_bits(dsa->p); return (ISC_R_SUCCESS); err: openssldsa_destroy(key); dst__privstruct_free(&priv, mctx); memset(&priv, 0, sizeof(priv)); return (ret); } static dst_func_t openssldsa_functions = { openssldsa_createctx, openssldsa_destroyctx, openssldsa_adddata, openssldsa_sign, openssldsa_verify, NULL, /*%< verify2 */ NULL, /*%< computesecret */ openssldsa_compare, NULL, /*%< paramcompare */ openssldsa_generate, openssldsa_isprivate, openssldsa_destroy, openssldsa_todns, openssldsa_fromdns, openssldsa_tofile, openssldsa_parse, NULL, /*%< cleanup */ NULL, /*%< fromlabel */ NULL, /*%< dump */ NULL, /*%< restore */ }; isc_result_t dst__openssldsa_init(dst_func_t **funcp) { REQUIRE(funcp != NULL); if (*funcp == NULL) *funcp = &openssldsa_functions; return (ISC_R_SUCCESS); } #else /* OPENSSL */ #include EMPTY_TRANSLATION_UNIT #endif /* OPENSSL */ /*! \file */ Index: stable/9/contrib/bind9/lib/dns/opensslecdsa_link.c =================================================================== --- stable/9/contrib/bind9/lib/dns/opensslecdsa_link.c (revision 287408) +++ stable/9/contrib/bind9/lib/dns/opensslecdsa_link.c (revision 287409) @@ -1,642 +1,640 @@ /* * Copyright (C) 2012-2014 Internet Systems Consortium, Inc. ("ISC") * * Permission to use, copy, modify, and/or distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY * AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT, * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE * OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR * PERFORMANCE OF THIS SOFTWARE. */ -/* $Id$ */ - #include #ifdef HAVE_OPENSSL_ECDSA #if !defined(HAVE_EVP_SHA256) || !defined(HAVE_EVP_SHA384) #error "ECDSA without EVP for SHA2?" #endif #include #include #include #include #include #include #include #include "dst_internal.h" #include "dst_openssl.h" #include "dst_parse.h" #include #include #include #include #ifndef NID_X9_62_prime256v1 #error "P-256 group is not known (NID_X9_62_prime256v1)" #endif #ifndef NID_secp384r1 #error "P-384 group is not known (NID_secp384r1)" #endif #define DST_RET(a) {ret = a; goto err;} static isc_result_t opensslecdsa_todns(const dst_key_t *key, isc_buffer_t *data); static isc_result_t opensslecdsa_createctx(dst_key_t *key, dst_context_t *dctx) { EVP_MD_CTX *evp_md_ctx; const EVP_MD *type = NULL; UNUSED(key); REQUIRE(dctx->key->key_alg == DST_ALG_ECDSA256 || dctx->key->key_alg == DST_ALG_ECDSA384); evp_md_ctx = EVP_MD_CTX_create(); if (evp_md_ctx == NULL) return (ISC_R_NOMEMORY); if (dctx->key->key_alg == DST_ALG_ECDSA256) type = EVP_sha256(); else type = EVP_sha384(); if (!EVP_DigestInit_ex(evp_md_ctx, type, NULL)) { EVP_MD_CTX_destroy(evp_md_ctx); return (dst__openssl_toresult3(dctx->category, "EVP_DigestInit_ex", ISC_R_FAILURE)); } dctx->ctxdata.evp_md_ctx = evp_md_ctx; return (ISC_R_SUCCESS); } static void opensslecdsa_destroyctx(dst_context_t *dctx) { EVP_MD_CTX *evp_md_ctx = dctx->ctxdata.evp_md_ctx; REQUIRE(dctx->key->key_alg == DST_ALG_ECDSA256 || dctx->key->key_alg == DST_ALG_ECDSA384); if (evp_md_ctx != NULL) { EVP_MD_CTX_destroy(evp_md_ctx); dctx->ctxdata.evp_md_ctx = NULL; } } static isc_result_t opensslecdsa_adddata(dst_context_t *dctx, const isc_region_t *data) { EVP_MD_CTX *evp_md_ctx = dctx->ctxdata.evp_md_ctx; REQUIRE(dctx->key->key_alg == DST_ALG_ECDSA256 || dctx->key->key_alg == DST_ALG_ECDSA384); if (!EVP_DigestUpdate(evp_md_ctx, data->base, data->length)) return (dst__openssl_toresult3(dctx->category, "EVP_DigestUpdate", ISC_R_FAILURE)); return (ISC_R_SUCCESS); } static int BN_bn2bin_fixed(BIGNUM *bn, unsigned char *buf, int size) { int bytes = size - BN_num_bytes(bn); while (bytes-- > 0) *buf++ = 0; BN_bn2bin(bn, buf); return (size); } static isc_result_t opensslecdsa_sign(dst_context_t *dctx, isc_buffer_t *sig) { isc_result_t ret; dst_key_t *key = dctx->key; isc_region_t r; ECDSA_SIG *ecdsasig; EVP_MD_CTX *evp_md_ctx = dctx->ctxdata.evp_md_ctx; EVP_PKEY *pkey = key->keydata.pkey; EC_KEY *eckey = EVP_PKEY_get1_EC_KEY(pkey); unsigned int dgstlen, siglen; unsigned char digest[EVP_MAX_MD_SIZE]; REQUIRE(key->key_alg == DST_ALG_ECDSA256 || key->key_alg == DST_ALG_ECDSA384); if (eckey == NULL) return (ISC_R_FAILURE); if (key->key_alg == DST_ALG_ECDSA256) siglen = DNS_SIG_ECDSA256SIZE; else siglen = DNS_SIG_ECDSA384SIZE; isc_buffer_availableregion(sig, &r); if (r.length < siglen) DST_RET(ISC_R_NOSPACE); if (!EVP_DigestFinal(evp_md_ctx, digest, &dgstlen)) DST_RET(dst__openssl_toresult3(dctx->category, "EVP_DigestFinal", ISC_R_FAILURE)); ecdsasig = ECDSA_do_sign(digest, dgstlen, eckey); if (ecdsasig == NULL) DST_RET(dst__openssl_toresult3(dctx->category, "ECDSA_do_sign", DST_R_SIGNFAILURE)); BN_bn2bin_fixed(ecdsasig->r, r.base, siglen / 2); - r.base += siglen / 2; + isc_region_consume(&r, siglen / 2); BN_bn2bin_fixed(ecdsasig->s, r.base, siglen / 2); - r.base += siglen / 2; + isc_region_consume(&r, siglen / 2); ECDSA_SIG_free(ecdsasig); isc_buffer_add(sig, siglen); ret = ISC_R_SUCCESS; err: if (eckey != NULL) EC_KEY_free(eckey); return (ret); } static isc_result_t opensslecdsa_verify(dst_context_t *dctx, const isc_region_t *sig) { isc_result_t ret; dst_key_t *key = dctx->key; int status; unsigned char *cp = sig->base; ECDSA_SIG *ecdsasig = NULL; EVP_MD_CTX *evp_md_ctx = dctx->ctxdata.evp_md_ctx; EVP_PKEY *pkey = key->keydata.pkey; EC_KEY *eckey = EVP_PKEY_get1_EC_KEY(pkey); unsigned int dgstlen, siglen; unsigned char digest[EVP_MAX_MD_SIZE]; REQUIRE(key->key_alg == DST_ALG_ECDSA256 || key->key_alg == DST_ALG_ECDSA384); if (eckey == NULL) return (ISC_R_FAILURE); if (key->key_alg == DST_ALG_ECDSA256) siglen = DNS_SIG_ECDSA256SIZE; else siglen = DNS_SIG_ECDSA384SIZE; if (sig->length != siglen) return (DST_R_VERIFYFAILURE); if (!EVP_DigestFinal_ex(evp_md_ctx, digest, &dgstlen)) DST_RET (dst__openssl_toresult3(dctx->category, "EVP_DigestFinal_ex", ISC_R_FAILURE)); ecdsasig = ECDSA_SIG_new(); if (ecdsasig == NULL) DST_RET (ISC_R_NOMEMORY); if (ecdsasig->r != NULL) BN_free(ecdsasig->r); ecdsasig->r = BN_bin2bn(cp, siglen / 2, NULL); cp += siglen / 2; if (ecdsasig->s != NULL) BN_free(ecdsasig->s); ecdsasig->s = BN_bin2bn(cp, siglen / 2, NULL); /* cp += siglen / 2; */ status = ECDSA_do_verify(digest, dgstlen, ecdsasig, eckey); switch (status) { case 1: ret = ISC_R_SUCCESS; break; case 0: ret = dst__openssl_toresult(DST_R_VERIFYFAILURE); break; default: ret = dst__openssl_toresult3(dctx->category, "ECDSA_do_verify", DST_R_VERIFYFAILURE); break; } err: if (ecdsasig != NULL) ECDSA_SIG_free(ecdsasig); if (eckey != NULL) EC_KEY_free(eckey); return (ret); } static isc_boolean_t opensslecdsa_compare(const dst_key_t *key1, const dst_key_t *key2) { isc_boolean_t ret; int status; EVP_PKEY *pkey1 = key1->keydata.pkey; EVP_PKEY *pkey2 = key2->keydata.pkey; EC_KEY *eckey1 = NULL; EC_KEY *eckey2 = NULL; const BIGNUM *priv1, *priv2; if (pkey1 == NULL && pkey2 == NULL) return (ISC_TRUE); else if (pkey1 == NULL || pkey2 == NULL) return (ISC_FALSE); eckey1 = EVP_PKEY_get1_EC_KEY(pkey1); eckey2 = EVP_PKEY_get1_EC_KEY(pkey2); if (eckey1 == NULL && eckey2 == NULL) { DST_RET (ISC_TRUE); } else if (eckey1 == NULL || eckey2 == NULL) DST_RET (ISC_FALSE); status = EVP_PKEY_cmp(pkey1, pkey2); if (status != 1) DST_RET (ISC_FALSE); priv1 = EC_KEY_get0_private_key(eckey1); priv2 = EC_KEY_get0_private_key(eckey2); if (priv1 != NULL || priv2 != NULL) { if (priv1 == NULL || priv2 == NULL) DST_RET (ISC_FALSE); if (BN_cmp(priv1, priv2) != 0) DST_RET (ISC_FALSE); } ret = ISC_TRUE; err: if (eckey1 != NULL) EC_KEY_free(eckey1); if (eckey2 != NULL) EC_KEY_free(eckey2); return (ret); } static isc_result_t opensslecdsa_generate(dst_key_t *key, int unused, void (*callback)(int)) { isc_result_t ret; EVP_PKEY *pkey; EC_KEY *eckey = NULL; int group_nid; REQUIRE(key->key_alg == DST_ALG_ECDSA256 || key->key_alg == DST_ALG_ECDSA384); UNUSED(unused); UNUSED(callback); if (key->key_alg == DST_ALG_ECDSA256) { group_nid = NID_X9_62_prime256v1; key->key_size = DNS_KEY_ECDSA256SIZE * 4; } else { group_nid = NID_secp384r1; key->key_size = DNS_KEY_ECDSA384SIZE * 4; } eckey = EC_KEY_new_by_curve_name(group_nid); if (eckey == NULL) return (dst__openssl_toresult2("EC_KEY_new_by_curve_name", DST_R_OPENSSLFAILURE)); if (EC_KEY_generate_key(eckey) != 1) DST_RET (dst__openssl_toresult2("EC_KEY_generate_key", DST_R_OPENSSLFAILURE)); pkey = EVP_PKEY_new(); if (pkey == NULL) DST_RET (ISC_R_NOMEMORY); if (!EVP_PKEY_set1_EC_KEY(pkey, eckey)) { EVP_PKEY_free(pkey); DST_RET (ISC_R_FAILURE); } key->keydata.pkey = pkey; ret = ISC_R_SUCCESS; err: if (eckey != NULL) EC_KEY_free(eckey); return (ret); } static isc_boolean_t opensslecdsa_isprivate(const dst_key_t *key) { isc_boolean_t ret; EVP_PKEY *pkey = key->keydata.pkey; EC_KEY *eckey = EVP_PKEY_get1_EC_KEY(pkey); ret = ISC_TF(eckey != NULL && EC_KEY_get0_private_key(eckey) != NULL); if (eckey != NULL) EC_KEY_free(eckey); return (ret); } static void opensslecdsa_destroy(dst_key_t *key) { EVP_PKEY *pkey = key->keydata.pkey; EVP_PKEY_free(pkey); key->keydata.pkey = NULL; } static isc_result_t opensslecdsa_todns(const dst_key_t *key, isc_buffer_t *data) { isc_result_t ret; EVP_PKEY *pkey; EC_KEY *eckey = NULL; isc_region_t r; int len; unsigned char *cp; unsigned char buf[DNS_KEY_ECDSA384SIZE + 1]; REQUIRE(key->keydata.pkey != NULL); pkey = key->keydata.pkey; eckey = EVP_PKEY_get1_EC_KEY(pkey); if (eckey == NULL) return (dst__openssl_toresult(ISC_R_FAILURE)); len = i2o_ECPublicKey(eckey, NULL); /* skip form */ len--; isc_buffer_availableregion(data, &r); if (r.length < (unsigned int) len) DST_RET (ISC_R_NOSPACE); cp = buf; if (!i2o_ECPublicKey(eckey, &cp)) DST_RET (dst__openssl_toresult(ISC_R_FAILURE)); memmove(r.base, buf + 1, len); isc_buffer_add(data, len); ret = ISC_R_SUCCESS; err: if (eckey != NULL) EC_KEY_free(eckey); return (ret); } static isc_result_t opensslecdsa_fromdns(dst_key_t *key, isc_buffer_t *data) { isc_result_t ret; EVP_PKEY *pkey; EC_KEY *eckey = NULL; isc_region_t r; int group_nid; unsigned int len; const unsigned char *cp; unsigned char buf[DNS_KEY_ECDSA384SIZE + 1]; REQUIRE(key->key_alg == DST_ALG_ECDSA256 || key->key_alg == DST_ALG_ECDSA384); if (key->key_alg == DST_ALG_ECDSA256) { len = DNS_KEY_ECDSA256SIZE; group_nid = NID_X9_62_prime256v1; } else { len = DNS_KEY_ECDSA384SIZE; group_nid = NID_secp384r1; } isc_buffer_remainingregion(data, &r); if (r.length == 0) return (ISC_R_SUCCESS); if (r.length < len) return (DST_R_INVALIDPUBLICKEY); eckey = EC_KEY_new_by_curve_name(group_nid); if (eckey == NULL) return (dst__openssl_toresult(DST_R_OPENSSLFAILURE)); buf[0] = POINT_CONVERSION_UNCOMPRESSED; memmove(buf + 1, r.base, len); cp = buf; if (o2i_ECPublicKey(&eckey, (const unsigned char **) &cp, (long) len + 1) == NULL) DST_RET (dst__openssl_toresult(DST_R_INVALIDPUBLICKEY)); if (EC_KEY_check_key(eckey) != 1) DST_RET (dst__openssl_toresult(DST_R_INVALIDPUBLICKEY)); pkey = EVP_PKEY_new(); if (pkey == NULL) DST_RET (ISC_R_NOMEMORY); if (!EVP_PKEY_set1_EC_KEY(pkey, eckey)) { EVP_PKEY_free(pkey); DST_RET (dst__openssl_toresult(ISC_R_FAILURE)); } isc_buffer_forward(data, len); key->keydata.pkey = pkey; key->key_size = len * 4; ret = ISC_R_SUCCESS; err: if (eckey != NULL) EC_KEY_free(eckey); return (ret); } static isc_result_t opensslecdsa_tofile(const dst_key_t *key, const char *directory) { isc_result_t ret; EVP_PKEY *pkey; EC_KEY *eckey = NULL; const BIGNUM *privkey; dst_private_t priv; unsigned char *buf = NULL; if (key->keydata.pkey == NULL) return (DST_R_NULLKEY); if (key->external) { priv.nelements = 0; return (dst__privstruct_writefile(key, &priv, directory)); } pkey = key->keydata.pkey; eckey = EVP_PKEY_get1_EC_KEY(pkey); if (eckey == NULL) return (dst__openssl_toresult(DST_R_OPENSSLFAILURE)); privkey = EC_KEY_get0_private_key(eckey); if (privkey == NULL) DST_RET (ISC_R_FAILURE); buf = isc_mem_get(key->mctx, BN_num_bytes(privkey)); if (buf == NULL) DST_RET (ISC_R_NOMEMORY); priv.elements[0].tag = TAG_ECDSA_PRIVATEKEY; priv.elements[0].length = BN_num_bytes(privkey); BN_bn2bin(privkey, buf); priv.elements[0].data = buf; priv.nelements = ECDSA_NTAGS; ret = dst__privstruct_writefile(key, &priv, directory); err: if (eckey != NULL) EC_KEY_free(eckey); if (buf != NULL) isc_mem_put(key->mctx, buf, BN_num_bytes(privkey)); return (ret); } static isc_result_t ecdsa_check(EC_KEY *eckey, dst_key_t *pub) { isc_result_t ret = ISC_R_FAILURE; EVP_PKEY *pkey; EC_KEY *pubeckey = NULL; const EC_POINT *pubkey; if (pub == NULL) return (ISC_R_SUCCESS); pkey = pub->keydata.pkey; if (pkey == NULL) return (ISC_R_SUCCESS); pubeckey = EVP_PKEY_get1_EC_KEY(pkey); if (pubeckey == NULL) return (ISC_R_SUCCESS); pubkey = EC_KEY_get0_public_key(pubeckey); if (pubkey == NULL) DST_RET (ISC_R_SUCCESS); if (EC_KEY_set_public_key(eckey, pubkey) != 1) DST_RET (ISC_R_SUCCESS); if (EC_KEY_check_key(eckey) == 1) DST_RET (ISC_R_SUCCESS); err: if (pubeckey != NULL) EC_KEY_free(pubeckey); return (ret); } static isc_result_t opensslecdsa_parse(dst_key_t *key, isc_lex_t *lexer, dst_key_t *pub) { dst_private_t priv; isc_result_t ret; EVP_PKEY *pkey, *pubpkey; EC_KEY *eckey = NULL, *pubeckey = NULL; const EC_POINT *pubkey; BIGNUM *privkey; int group_nid; isc_mem_t *mctx = key->mctx; REQUIRE(key->key_alg == DST_ALG_ECDSA256 || key->key_alg == DST_ALG_ECDSA384); if (key->key_alg == DST_ALG_ECDSA256) group_nid = NID_X9_62_prime256v1; else group_nid = NID_secp384r1; eckey = EC_KEY_new_by_curve_name(group_nid); if (eckey == NULL) return (dst__openssl_toresult(DST_R_OPENSSLFAILURE)); /* read private key file */ ret = dst__privstruct_parse(key, DST_ALG_ECDSA256, lexer, mctx, &priv); if (ret != ISC_R_SUCCESS) goto err; if (key->external) { /* * Copy the public key to this new key. */ if (pub == NULL) DST_RET(DST_R_INVALIDPRIVATEKEY); pubpkey = pub->keydata.pkey; pubeckey = EVP_PKEY_get1_EC_KEY(pubpkey); if (pubeckey == NULL) DST_RET(DST_R_INVALIDPRIVATEKEY); pubkey = EC_KEY_get0_public_key(pubeckey); if (pubkey == NULL) DST_RET(DST_R_INVALIDPRIVATEKEY); if (EC_KEY_set_public_key(eckey, pubkey) != 1) DST_RET(DST_R_INVALIDPRIVATEKEY); if (EC_KEY_check_key(eckey) != 1) DST_RET(DST_R_INVALIDPRIVATEKEY); } else { privkey = BN_bin2bn(priv.elements[0].data, priv.elements[0].length, NULL); if (privkey == NULL) DST_RET(ISC_R_NOMEMORY); if (!EC_KEY_set_private_key(eckey, privkey)) DST_RET(ISC_R_NOMEMORY); if (ecdsa_check(eckey, pub) != ISC_R_SUCCESS) DST_RET(DST_R_INVALIDPRIVATEKEY); dst__privstruct_free(&priv, mctx); memset(&priv, 0, sizeof(priv)); } pkey = EVP_PKEY_new(); if (pkey == NULL) DST_RET (ISC_R_NOMEMORY); if (!EVP_PKEY_set1_EC_KEY(pkey, eckey)) { EVP_PKEY_free(pkey); DST_RET (dst__openssl_toresult(DST_R_OPENSSLFAILURE)); } key->keydata.pkey = pkey; if (key->key_alg == DST_ALG_ECDSA256) key->key_size = DNS_KEY_ECDSA256SIZE * 4; else key->key_size = DNS_KEY_ECDSA384SIZE * 4; ret = ISC_R_SUCCESS; err: if (eckey != NULL) EC_KEY_free(eckey); if (pubeckey != NULL) EC_KEY_free(pubeckey); dst__privstruct_free(&priv, mctx); memset(&priv, 0, sizeof(priv)); return (ret); } static dst_func_t opensslecdsa_functions = { opensslecdsa_createctx, opensslecdsa_destroyctx, opensslecdsa_adddata, opensslecdsa_sign, opensslecdsa_verify, NULL, /*%< verify2 */ NULL, /*%< computesecret */ opensslecdsa_compare, NULL, /*%< paramcompare */ opensslecdsa_generate, opensslecdsa_isprivate, opensslecdsa_destroy, opensslecdsa_todns, opensslecdsa_fromdns, opensslecdsa_tofile, opensslecdsa_parse, NULL, /*%< cleanup */ NULL, /*%< fromlabel */ NULL, /*%< dump */ NULL, /*%< restore */ }; isc_result_t dst__opensslecdsa_init(dst_func_t **funcp) { REQUIRE(funcp != NULL); if (*funcp == NULL) *funcp = &opensslecdsa_functions; return (ISC_R_SUCCESS); } #else /* HAVE_OPENSSL_ECDSA */ #include EMPTY_TRANSLATION_UNIT #endif /* HAVE_OPENSSL_ECDSA */ /*! \file */ Index: stable/9/contrib/bind9/lib/dns/opensslrsa_link.c =================================================================== --- stable/9/contrib/bind9/lib/dns/opensslrsa_link.c (revision 287408) +++ stable/9/contrib/bind9/lib/dns/opensslrsa_link.c (revision 287409) @@ -1,1503 +1,1505 @@ /* * Copyright (C) 2004-2009, 2011-2014 Internet Systems Consortium, Inc. ("ISC") * Copyright (C) 2000-2003 Internet Software Consortium. * * Permission to use, copy, modify, and/or distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY * AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT, * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE * OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR * PERFORMANCE OF THIS SOFTWARE. */ /* * Principal Author: Brian Wellington * $Id$ */ #ifdef OPENSSL #include #ifndef USE_EVP #if !defined(HAVE_EVP_SHA256) || !defined(HAVE_EVP_SHA512) #define USE_EVP 0 #else #define USE_EVP 1 #endif #endif #include #include #include #include #include #include #include #include #include "dst_internal.h" #include "dst_openssl.h" #include "dst_parse.h" #include #include #include #if OPENSSL_VERSION_NUMBER > 0x00908000L #include #endif #ifdef USE_ENGINE #include #endif /* * Limit the size of public exponents. */ #ifndef RSA_MAX_PUBEXP_BITS #define RSA_MAX_PUBEXP_BITS 35 #endif /* * We don't use configure for windows so enforce the OpenSSL version * here. Unlike with configure we don't support overriding this test. */ #ifdef WIN32 #if !((OPENSSL_VERSION_NUMBER >= 0x009070cfL && \ OPENSSL_VERSION_NUMBER < 0x00908000L) || \ OPENSSL_VERSION_NUMBER >= 0x0090804fL) #error Please upgrade OpenSSL to 0.9.8d/0.9.7l or greater. #endif #endif /* * XXXMPA Temporarily disable RSA_BLINDING as it requires * good quality random data that cannot currently be guaranteed. * XXXMPA Find which versions of openssl use pseudo random data * and set RSA_FLAG_BLINDING for those. */ #if 0 #if OPENSSL_VERSION_NUMBER < 0x0090601fL #define SET_FLAGS(rsa) \ do { \ (rsa)->flags &= ~(RSA_FLAG_CACHE_PUBLIC | RSA_FLAG_CACHE_PRIVATE); \ (rsa)->flags |= RSA_FLAG_BLINDING; \ } while (0) #else #define SET_FLAGS(rsa) \ do { \ (rsa)->flags |= RSA_FLAG_BLINDING; \ } while (0) #endif #endif #if OPENSSL_VERSION_NUMBER < 0x0090601fL #define SET_FLAGS(rsa) \ do { \ (rsa)->flags &= ~(RSA_FLAG_CACHE_PUBLIC | RSA_FLAG_CACHE_PRIVATE); \ (rsa)->flags &= ~RSA_FLAG_BLINDING; \ } while (0) #elif defined(RSA_FLAG_NO_BLINDING) #define SET_FLAGS(rsa) \ do { \ (rsa)->flags &= ~RSA_FLAG_BLINDING; \ (rsa)->flags |= RSA_FLAG_NO_BLINDING; \ } while (0) #else #define SET_FLAGS(rsa) \ do { \ (rsa)->flags &= ~RSA_FLAG_BLINDING; \ } while (0) #endif #define DST_RET(a) {ret = a; goto err;} static isc_result_t opensslrsa_todns(const dst_key_t *key, isc_buffer_t *data); static isc_result_t opensslrsa_createctx(dst_key_t *key, dst_context_t *dctx) { #if USE_EVP EVP_MD_CTX *evp_md_ctx; const EVP_MD *type = NULL; #endif UNUSED(key); REQUIRE(dctx->key->key_alg == DST_ALG_RSAMD5 || dctx->key->key_alg == DST_ALG_RSASHA1 || dctx->key->key_alg == DST_ALG_NSEC3RSASHA1 || dctx->key->key_alg == DST_ALG_RSASHA256 || dctx->key->key_alg == DST_ALG_RSASHA512); #if USE_EVP evp_md_ctx = EVP_MD_CTX_create(); if (evp_md_ctx == NULL) return (ISC_R_NOMEMORY); switch (dctx->key->key_alg) { case DST_ALG_RSAMD5: type = EVP_md5(); /* MD5 + RSA */ break; case DST_ALG_RSASHA1: case DST_ALG_NSEC3RSASHA1: type = EVP_sha1(); /* SHA1 + RSA */ break; #ifdef HAVE_EVP_SHA256 case DST_ALG_RSASHA256: type = EVP_sha256(); /* SHA256 + RSA */ break; #endif #ifdef HAVE_EVP_SHA512 case DST_ALG_RSASHA512: type = EVP_sha512(); break; #endif default: INSIST(0); } if (!EVP_DigestInit_ex(evp_md_ctx, type, NULL)) { EVP_MD_CTX_destroy(evp_md_ctx); return (dst__openssl_toresult3(dctx->category, "EVP_DigestInit_ex", ISC_R_FAILURE)); } dctx->ctxdata.evp_md_ctx = evp_md_ctx; #else switch (dctx->key->key_alg) { case DST_ALG_RSAMD5: { isc_md5_t *md5ctx; md5ctx = isc_mem_get(dctx->mctx, sizeof(isc_md5_t)); if (md5ctx == NULL) return (ISC_R_NOMEMORY); isc_md5_init(md5ctx); dctx->ctxdata.md5ctx = md5ctx; } break; case DST_ALG_RSASHA1: case DST_ALG_NSEC3RSASHA1: { isc_sha1_t *sha1ctx; sha1ctx = isc_mem_get(dctx->mctx, sizeof(isc_sha1_t)); if (sha1ctx == NULL) return (ISC_R_NOMEMORY); isc_sha1_init(sha1ctx); dctx->ctxdata.sha1ctx = sha1ctx; } break; case DST_ALG_RSASHA256: { isc_sha256_t *sha256ctx; sha256ctx = isc_mem_get(dctx->mctx, sizeof(isc_sha256_t)); if (sha256ctx == NULL) return (ISC_R_NOMEMORY); isc_sha256_init(sha256ctx); dctx->ctxdata.sha256ctx = sha256ctx; } break; case DST_ALG_RSASHA512: { isc_sha512_t *sha512ctx; sha512ctx = isc_mem_get(dctx->mctx, sizeof(isc_sha512_t)); if (sha512ctx == NULL) return (ISC_R_NOMEMORY); isc_sha512_init(sha512ctx); dctx->ctxdata.sha512ctx = sha512ctx; } break; default: INSIST(0); } #endif return (ISC_R_SUCCESS); } static void opensslrsa_destroyctx(dst_context_t *dctx) { #if USE_EVP EVP_MD_CTX *evp_md_ctx = dctx->ctxdata.evp_md_ctx; #endif REQUIRE(dctx->key->key_alg == DST_ALG_RSAMD5 || dctx->key->key_alg == DST_ALG_RSASHA1 || dctx->key->key_alg == DST_ALG_NSEC3RSASHA1 || dctx->key->key_alg == DST_ALG_RSASHA256 || dctx->key->key_alg == DST_ALG_RSASHA512); #if USE_EVP if (evp_md_ctx != NULL) { EVP_MD_CTX_destroy(evp_md_ctx); dctx->ctxdata.evp_md_ctx = NULL; } #else switch (dctx->key->key_alg) { case DST_ALG_RSAMD5: { isc_md5_t *md5ctx = dctx->ctxdata.md5ctx; if (md5ctx != NULL) { isc_md5_invalidate(md5ctx); isc_mem_put(dctx->mctx, md5ctx, sizeof(isc_md5_t)); dctx->ctxdata.md5ctx = NULL; } } break; case DST_ALG_RSASHA1: case DST_ALG_NSEC3RSASHA1: { isc_sha1_t *sha1ctx = dctx->ctxdata.sha1ctx; if (sha1ctx != NULL) { isc_sha1_invalidate(sha1ctx); isc_mem_put(dctx->mctx, sha1ctx, sizeof(isc_sha1_t)); dctx->ctxdata.sha1ctx = NULL; } } break; case DST_ALG_RSASHA256: { isc_sha256_t *sha256ctx = dctx->ctxdata.sha256ctx; if (sha256ctx != NULL) { isc_sha256_invalidate(sha256ctx); isc_mem_put(dctx->mctx, sha256ctx, sizeof(isc_sha256_t)); dctx->ctxdata.sha256ctx = NULL; } } break; case DST_ALG_RSASHA512: { isc_sha512_t *sha512ctx = dctx->ctxdata.sha512ctx; if (sha512ctx != NULL) { isc_sha512_invalidate(sha512ctx); isc_mem_put(dctx->mctx, sha512ctx, sizeof(isc_sha512_t)); dctx->ctxdata.sha512ctx = NULL; } } break; default: INSIST(0); } #endif } static isc_result_t opensslrsa_adddata(dst_context_t *dctx, const isc_region_t *data) { #if USE_EVP EVP_MD_CTX *evp_md_ctx = dctx->ctxdata.evp_md_ctx; #endif REQUIRE(dctx->key->key_alg == DST_ALG_RSAMD5 || dctx->key->key_alg == DST_ALG_RSASHA1 || dctx->key->key_alg == DST_ALG_NSEC3RSASHA1 || dctx->key->key_alg == DST_ALG_RSASHA256 || dctx->key->key_alg == DST_ALG_RSASHA512); #if USE_EVP if (!EVP_DigestUpdate(evp_md_ctx, data->base, data->length)) { return (dst__openssl_toresult3(dctx->category, "EVP_DigestUpdate", ISC_R_FAILURE)); } #else switch (dctx->key->key_alg) { case DST_ALG_RSAMD5: { isc_md5_t *md5ctx = dctx->ctxdata.md5ctx; isc_md5_update(md5ctx, data->base, data->length); } break; case DST_ALG_RSASHA1: case DST_ALG_NSEC3RSASHA1: { isc_sha1_t *sha1ctx = dctx->ctxdata.sha1ctx; isc_sha1_update(sha1ctx, data->base, data->length); } break; case DST_ALG_RSASHA256: { isc_sha256_t *sha256ctx = dctx->ctxdata.sha256ctx; isc_sha256_update(sha256ctx, data->base, data->length); } break; case DST_ALG_RSASHA512: { isc_sha512_t *sha512ctx = dctx->ctxdata.sha512ctx; isc_sha512_update(sha512ctx, data->base, data->length); } break; default: INSIST(0); } #endif return (ISC_R_SUCCESS); } #if ! USE_EVP && OPENSSL_VERSION_NUMBER < 0x00908000L /* * Digest prefixes from RFC 5702. */ static unsigned char sha256_prefix[] = { 0x30, 0x31, 0x30, 0x0d, 0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x01, 0x05, 0x00, 0x04, 0x20}; static unsigned char sha512_prefix[] = { 0x30, 0x51, 0x30, 0x0d, 0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x03, 0x05, 0x00, 0x04, 0x40}; #define PREFIXLEN sizeof(sha512_prefix) #else #define PREFIXLEN 0 #endif static isc_result_t opensslrsa_sign(dst_context_t *dctx, isc_buffer_t *sig) { dst_key_t *key = dctx->key; isc_region_t r; unsigned int siglen = 0; #if USE_EVP EVP_MD_CTX *evp_md_ctx = dctx->ctxdata.evp_md_ctx; EVP_PKEY *pkey = key->keydata.pkey; #else RSA *rsa = key->keydata.rsa; /* note: ISC_SHA512_DIGESTLENGTH >= ISC_*_DIGESTLENGTH */ unsigned char digest[PREFIXLEN + ISC_SHA512_DIGESTLENGTH]; int status; int type = 0; unsigned int digestlen = 0; #if OPENSSL_VERSION_NUMBER < 0x00908000L unsigned int prefixlen = 0; const unsigned char *prefix = NULL; #endif #endif REQUIRE(dctx->key->key_alg == DST_ALG_RSAMD5 || dctx->key->key_alg == DST_ALG_RSASHA1 || dctx->key->key_alg == DST_ALG_NSEC3RSASHA1 || dctx->key->key_alg == DST_ALG_RSASHA256 || dctx->key->key_alg == DST_ALG_RSASHA512); isc_buffer_availableregion(sig, &r); #if USE_EVP if (r.length < (unsigned int) EVP_PKEY_size(pkey)) return (ISC_R_NOSPACE); if (!EVP_SignFinal(evp_md_ctx, r.base, &siglen, pkey)) { return (dst__openssl_toresult3(dctx->category, "EVP_SignFinal", ISC_R_FAILURE)); } #else if (r.length < (unsigned int) RSA_size(rsa)) return (ISC_R_NOSPACE); switch (dctx->key->key_alg) { case DST_ALG_RSAMD5: { isc_md5_t *md5ctx = dctx->ctxdata.md5ctx; isc_md5_final(md5ctx, digest); type = NID_md5; digestlen = ISC_MD5_DIGESTLENGTH; } break; case DST_ALG_RSASHA1: case DST_ALG_NSEC3RSASHA1: { isc_sha1_t *sha1ctx = dctx->ctxdata.sha1ctx; isc_sha1_final(sha1ctx, digest); type = NID_sha1; digestlen = ISC_SHA1_DIGESTLENGTH; } break; case DST_ALG_RSASHA256: { isc_sha256_t *sha256ctx = dctx->ctxdata.sha256ctx; isc_sha256_final(digest, sha256ctx); digestlen = ISC_SHA256_DIGESTLENGTH; #if OPENSSL_VERSION_NUMBER < 0x00908000L prefix = sha256_prefix; prefixlen = sizeof(sha256_prefix); #else type = NID_sha256; #endif } break; case DST_ALG_RSASHA512: { isc_sha512_t *sha512ctx = dctx->ctxdata.sha512ctx; isc_sha512_final(digest, sha512ctx); digestlen = ISC_SHA512_DIGESTLENGTH; #if OPENSSL_VERSION_NUMBER < 0x00908000L prefix = sha512_prefix; prefixlen = sizeof(sha512_prefix); #else type = NID_sha512; #endif } break; default: INSIST(0); } #if OPENSSL_VERSION_NUMBER < 0x00908000L switch (dctx->key->key_alg) { case DST_ALG_RSAMD5: case DST_ALG_RSASHA1: case DST_ALG_NSEC3RSASHA1: INSIST(type != 0); status = RSA_sign(type, digest, digestlen, r.base, &siglen, rsa); break; case DST_ALG_RSASHA256: case DST_ALG_RSASHA512: INSIST(prefix != NULL); INSIST(prefixlen != 0); INSIST(prefixlen + digestlen <= sizeof(digest)); memmove(digest + prefixlen, digest, digestlen); memmove(digest, prefix, prefixlen); status = RSA_private_encrypt(digestlen + prefixlen, digest, r.base, rsa, RSA_PKCS1_PADDING); if (status < 0) status = 0; else siglen = status; break; default: INSIST(0); } #else INSIST(type != 0); status = RSA_sign(type, digest, digestlen, r.base, &siglen, rsa); #endif if (status == 0) return (dst__openssl_toresult3(dctx->category, "RSA_sign", DST_R_OPENSSLFAILURE)); #endif isc_buffer_add(sig, siglen); return (ISC_R_SUCCESS); } static isc_result_t opensslrsa_verify2(dst_context_t *dctx, int maxbits, const isc_region_t *sig) { dst_key_t *key = dctx->key; int status = 0; #if USE_EVP EVP_MD_CTX *evp_md_ctx = dctx->ctxdata.evp_md_ctx; EVP_PKEY *pkey = key->keydata.pkey; RSA *rsa; int bits; #else /* note: ISC_SHA512_DIGESTLENGTH >= ISC_*_DIGESTLENGTH */ unsigned char digest[ISC_SHA512_DIGESTLENGTH]; int type = 0; unsigned int digestlen = 0; RSA *rsa = key->keydata.rsa; #if OPENSSL_VERSION_NUMBER < 0x00908000L unsigned int prefixlen = 0; const unsigned char *prefix = NULL; #endif #endif REQUIRE(dctx->key->key_alg == DST_ALG_RSAMD5 || dctx->key->key_alg == DST_ALG_RSASHA1 || dctx->key->key_alg == DST_ALG_NSEC3RSASHA1 || dctx->key->key_alg == DST_ALG_RSASHA256 || dctx->key->key_alg == DST_ALG_RSASHA512); #if USE_EVP rsa = EVP_PKEY_get1_RSA(pkey); if (rsa == NULL) return (dst__openssl_toresult(DST_R_OPENSSLFAILURE)); bits = BN_num_bits(rsa->e); RSA_free(rsa); if (bits > maxbits && maxbits != 0) return (DST_R_VERIFYFAILURE); status = EVP_VerifyFinal(evp_md_ctx, sig->base, sig->length, pkey); switch (status) { case 1: return (ISC_R_SUCCESS); case 0: return (dst__openssl_toresult(DST_R_VERIFYFAILURE)); default: return (dst__openssl_toresult3(dctx->category, "EVP_VerifyFinal", DST_R_VERIFYFAILURE)); } #else if (BN_num_bits(rsa->e) > maxbits && maxbits != 0) return (DST_R_VERIFYFAILURE); switch (dctx->key->key_alg) { case DST_ALG_RSAMD5: { isc_md5_t *md5ctx = dctx->ctxdata.md5ctx; isc_md5_final(md5ctx, digest); type = NID_md5; digestlen = ISC_MD5_DIGESTLENGTH; } break; case DST_ALG_RSASHA1: case DST_ALG_NSEC3RSASHA1: { isc_sha1_t *sha1ctx = dctx->ctxdata.sha1ctx; isc_sha1_final(sha1ctx, digest); type = NID_sha1; digestlen = ISC_SHA1_DIGESTLENGTH; } break; case DST_ALG_RSASHA256: { isc_sha256_t *sha256ctx = dctx->ctxdata.sha256ctx; isc_sha256_final(digest, sha256ctx); digestlen = ISC_SHA256_DIGESTLENGTH; #if OPENSSL_VERSION_NUMBER < 0x00908000L prefix = sha256_prefix; prefixlen = sizeof(sha256_prefix); #else type = NID_sha256; #endif } break; case DST_ALG_RSASHA512: { isc_sha512_t *sha512ctx = dctx->ctxdata.sha512ctx; isc_sha512_final(digest, sha512ctx); digestlen = ISC_SHA512_DIGESTLENGTH; #if OPENSSL_VERSION_NUMBER < 0x00908000L prefix = sha512_prefix; prefixlen = sizeof(sha512_prefix); #else type = NID_sha512; #endif } break; default: INSIST(0); } if (sig->length != (unsigned int) RSA_size(rsa)) return (DST_R_VERIFYFAILURE); #if OPENSSL_VERSION_NUMBER < 0x00908000L switch (dctx->key->key_alg) { case DST_ALG_RSAMD5: case DST_ALG_RSASHA1: case DST_ALG_NSEC3RSASHA1: INSIST(type != 0); status = RSA_verify(type, digest, digestlen, sig->base, RSA_size(rsa), rsa); break; case DST_ALG_RSASHA256: case DST_ALG_RSASHA512: { /* * 1024 is big enough for all valid RSA bit sizes * for use with DNSSEC. */ unsigned char original[PREFIXLEN + 1024]; INSIST(prefix != NULL); INSIST(prefixlen != 0U); if (RSA_size(rsa) > (int)sizeof(original)) return (DST_R_VERIFYFAILURE); status = RSA_public_decrypt(sig->length, sig->base, original, rsa, RSA_PKCS1_PADDING); if (status <= 0) return (dst__openssl_toresult3( dctx->category, "RSA_public_decrypt", DST_R_VERIFYFAILURE)); if (status != (int)(prefixlen + digestlen)) return (DST_R_VERIFYFAILURE); if (memcmp(original, prefix, prefixlen)) return (DST_R_VERIFYFAILURE); if (memcmp(original + prefixlen, digest, digestlen)) return (DST_R_VERIFYFAILURE); status = 1; } break; default: INSIST(0); } #else INSIST(type != 0); status = RSA_verify(type, digest, digestlen, sig->base, RSA_size(rsa), rsa); #endif if (status != 1) return (dst__openssl_toresult(DST_R_VERIFYFAILURE)); return (ISC_R_SUCCESS); #endif } static isc_result_t opensslrsa_verify(dst_context_t *dctx, const isc_region_t *sig) { return (opensslrsa_verify2(dctx, 0, sig)); } static isc_boolean_t opensslrsa_compare(const dst_key_t *key1, const dst_key_t *key2) { int status; RSA *rsa1 = NULL, *rsa2 = NULL; #if USE_EVP EVP_PKEY *pkey1, *pkey2; #endif #if USE_EVP pkey1 = key1->keydata.pkey; pkey2 = key2->keydata.pkey; /* * The pkey reference will keep these around after * the RSA_free() call. */ if (pkey1 != NULL) { rsa1 = EVP_PKEY_get1_RSA(pkey1); RSA_free(rsa1); } if (pkey2 != NULL) { rsa2 = EVP_PKEY_get1_RSA(pkey2); RSA_free(rsa2); } #else rsa1 = key1->keydata.rsa; rsa2 = key2->keydata.rsa; #endif if (rsa1 == NULL && rsa2 == NULL) return (ISC_TRUE); else if (rsa1 == NULL || rsa2 == NULL) return (ISC_FALSE); status = BN_cmp(rsa1->n, rsa2->n) || BN_cmp(rsa1->e, rsa2->e); if (status != 0) return (ISC_FALSE); #if USE_EVP if ((rsa1->flags & RSA_FLAG_EXT_PKEY) != 0 || (rsa2->flags & RSA_FLAG_EXT_PKEY) != 0) { if ((rsa1->flags & RSA_FLAG_EXT_PKEY) == 0 || (rsa2->flags & RSA_FLAG_EXT_PKEY) == 0) return (ISC_FALSE); /* * Can't compare private parameters, BTW does it make sense? */ return (ISC_TRUE); } #endif if (rsa1->d != NULL || rsa2->d != NULL) { if (rsa1->d == NULL || rsa2->d == NULL) return (ISC_FALSE); status = BN_cmp(rsa1->d, rsa2->d) || BN_cmp(rsa1->p, rsa2->p) || BN_cmp(rsa1->q, rsa2->q); if (status != 0) return (ISC_FALSE); } return (ISC_TRUE); } #if OPENSSL_VERSION_NUMBER > 0x00908000L static int progress_cb(int p, int n, BN_GENCB *cb) { union { void *dptr; void (*fptr)(int); } u; UNUSED(n); u.dptr = cb->arg; if (u.fptr != NULL) u.fptr(p); return (1); } #endif static isc_result_t opensslrsa_generate(dst_key_t *key, int exp, void (*callback)(int)) { #if OPENSSL_VERSION_NUMBER > 0x00908000L isc_result_t ret = DST_R_OPENSSLFAILURE; BN_GENCB cb; union { void *dptr; void (*fptr)(int); } u; RSA *rsa = RSA_new(); BIGNUM *e = BN_new(); #if USE_EVP EVP_PKEY *pkey = EVP_PKEY_new(); #endif if (rsa == NULL || e == NULL) goto err; #if USE_EVP if (pkey == NULL) goto err; if (!EVP_PKEY_set1_RSA(pkey, rsa)) goto err; #endif if (exp == 0) { /* RSA_F4 0x10001 */ BN_set_bit(e, 0); BN_set_bit(e, 16); } else { /* (phased-out) F5 0x100000001 */ BN_set_bit(e, 0); BN_set_bit(e, 32); } if (callback == NULL) { BN_GENCB_set_old(&cb, NULL, NULL); } else { u.fptr = callback; BN_GENCB_set(&cb, &progress_cb, u.dptr); } if (RSA_generate_key_ex(rsa, key->key_size, e, &cb)) { BN_free(e); SET_FLAGS(rsa); #if USE_EVP key->keydata.pkey = pkey; RSA_free(rsa); #else key->keydata.rsa = rsa; #endif return (ISC_R_SUCCESS); } ret = dst__openssl_toresult2("RSA_generate_key_ex", DST_R_OPENSSLFAILURE); err: #if USE_EVP if (pkey != NULL) EVP_PKEY_free(pkey); #endif if (e != NULL) BN_free(e); if (rsa != NULL) RSA_free(rsa); return (dst__openssl_toresult(ret)); #else RSA *rsa; unsigned long e; #if USE_EVP EVP_PKEY *pkey = EVP_PKEY_new(); UNUSED(callback); if (pkey == NULL) return (ISC_R_NOMEMORY); #else UNUSED(callback); #endif if (exp == 0) e = RSA_F4; else e = 0x40000003; rsa = RSA_generate_key(key->key_size, e, NULL, NULL); if (rsa == NULL) { #if USE_EVP EVP_PKEY_free(pkey); #endif return (dst__openssl_toresult2("RSA_generate_key", DST_R_OPENSSLFAILURE)); } SET_FLAGS(rsa); #if USE_EVP if (!EVP_PKEY_set1_RSA(pkey, rsa)) { EVP_PKEY_free(pkey); RSA_free(rsa); return (dst__openssl_toresult(DST_R_OPENSSLFAILURE)); } key->keydata.pkey = pkey; RSA_free(rsa); #else key->keydata.rsa = rsa; #endif return (ISC_R_SUCCESS); #endif } static isc_boolean_t opensslrsa_isprivate(const dst_key_t *key) { #if USE_EVP RSA *rsa = EVP_PKEY_get1_RSA(key->keydata.pkey); INSIST(rsa != NULL); RSA_free(rsa); /* key->keydata.pkey still has a reference so rsa is still valid. */ #else RSA *rsa = key->keydata.rsa; #endif if (rsa != NULL && (rsa->flags & RSA_FLAG_EXT_PKEY) != 0) return (ISC_TRUE); return (ISC_TF(rsa != NULL && rsa->d != NULL)); } static void opensslrsa_destroy(dst_key_t *key) { #if USE_EVP EVP_PKEY *pkey = key->keydata.pkey; EVP_PKEY_free(pkey); key->keydata.pkey = NULL; #else RSA *rsa = key->keydata.rsa; RSA_free(rsa); key->keydata.rsa = NULL; #endif } static isc_result_t opensslrsa_todns(const dst_key_t *key, isc_buffer_t *data) { isc_region_t r; unsigned int e_bytes; unsigned int mod_bytes; isc_result_t ret; RSA *rsa; #if USE_EVP EVP_PKEY *pkey; #endif #if USE_EVP REQUIRE(key->keydata.pkey != NULL); #else REQUIRE(key->keydata.rsa != NULL); #endif #if USE_EVP pkey = key->keydata.pkey; rsa = EVP_PKEY_get1_RSA(pkey); if (rsa == NULL) return (dst__openssl_toresult(DST_R_OPENSSLFAILURE)); #else rsa = key->keydata.rsa; #endif isc_buffer_availableregion(data, &r); e_bytes = BN_num_bytes(rsa->e); mod_bytes = BN_num_bytes(rsa->n); if (e_bytes < 256) { /*%< key exponent is <= 2040 bits */ if (r.length < 1) DST_RET(ISC_R_NOSPACE); isc_buffer_putuint8(data, (isc_uint8_t) e_bytes); isc_region_consume(&r, 1); } else { if (r.length < 3) DST_RET(ISC_R_NOSPACE); isc_buffer_putuint8(data, 0); isc_buffer_putuint16(data, (isc_uint16_t) e_bytes); isc_region_consume(&r, 3); } if (r.length < e_bytes + mod_bytes) DST_RET(ISC_R_NOSPACE); BN_bn2bin(rsa->e, r.base); isc_region_consume(&r, e_bytes); BN_bn2bin(rsa->n, r.base); isc_buffer_add(data, e_bytes + mod_bytes); ret = ISC_R_SUCCESS; err: #if USE_EVP if (rsa != NULL) RSA_free(rsa); #endif return (ret); } static isc_result_t opensslrsa_fromdns(dst_key_t *key, isc_buffer_t *data) { RSA *rsa; isc_region_t r; unsigned int e_bytes; + unsigned int length; #if USE_EVP EVP_PKEY *pkey; #endif isc_buffer_remainingregion(data, &r); if (r.length == 0) return (ISC_R_SUCCESS); + length = r.length; rsa = RSA_new(); if (rsa == NULL) return (dst__openssl_toresult(ISC_R_NOMEMORY)); SET_FLAGS(rsa); if (r.length < 1) { RSA_free(rsa); return (DST_R_INVALIDPUBLICKEY); } - e_bytes = *r.base++; - r.length--; + e_bytes = *r.base; + isc_region_consume(&r, 1); if (e_bytes == 0) { if (r.length < 2) { RSA_free(rsa); return (DST_R_INVALIDPUBLICKEY); } - e_bytes = ((*r.base++) << 8); - e_bytes += *r.base++; - r.length -= 2; + e_bytes = (*r.base) << 8; + isc_region_consume(&r, 1); + e_bytes += *r.base; + isc_region_consume(&r, 1); } if (r.length < e_bytes) { RSA_free(rsa); return (DST_R_INVALIDPUBLICKEY); } rsa->e = BN_bin2bn(r.base, e_bytes, NULL); - r.base += e_bytes; - r.length -= e_bytes; + isc_region_consume(&r, e_bytes); rsa->n = BN_bin2bn(r.base, r.length, NULL); key->key_size = BN_num_bits(rsa->n); - isc_buffer_forward(data, r.length); + isc_buffer_forward(data, length); #if USE_EVP pkey = EVP_PKEY_new(); if (pkey == NULL) { RSA_free(rsa); return (ISC_R_NOMEMORY); } if (!EVP_PKEY_set1_RSA(pkey, rsa)) { EVP_PKEY_free(pkey); RSA_free(rsa); return (dst__openssl_toresult(DST_R_OPENSSLFAILURE)); } key->keydata.pkey = pkey; RSA_free(rsa); #else key->keydata.rsa = rsa; #endif return (ISC_R_SUCCESS); } static isc_result_t opensslrsa_tofile(const dst_key_t *key, const char *directory) { int i; RSA *rsa; dst_private_t priv; unsigned char *bufs[8]; isc_result_t result; #if USE_EVP if (key->keydata.pkey == NULL) return (DST_R_NULLKEY); rsa = EVP_PKEY_get1_RSA(key->keydata.pkey); if (rsa == NULL) return (dst__openssl_toresult(DST_R_OPENSSLFAILURE)); #else if (key->keydata.rsa == NULL) return (DST_R_NULLKEY); rsa = key->keydata.rsa; #endif memset(bufs, 0, sizeof(bufs)); if (key->external) { priv.nelements = 0; result = dst__privstruct_writefile(key, &priv, directory); goto fail; } for (i = 0; i < 8; i++) { bufs[i] = isc_mem_get(key->mctx, BN_num_bytes(rsa->n)); if (bufs[i] == NULL) { result = ISC_R_NOMEMORY; goto fail; } } i = 0; priv.elements[i].tag = TAG_RSA_MODULUS; priv.elements[i].length = BN_num_bytes(rsa->n); BN_bn2bin(rsa->n, bufs[i]); priv.elements[i].data = bufs[i]; i++; priv.elements[i].tag = TAG_RSA_PUBLICEXPONENT; priv.elements[i].length = BN_num_bytes(rsa->e); BN_bn2bin(rsa->e, bufs[i]); priv.elements[i].data = bufs[i]; i++; if (rsa->d != NULL) { priv.elements[i].tag = TAG_RSA_PRIVATEEXPONENT; priv.elements[i].length = BN_num_bytes(rsa->d); BN_bn2bin(rsa->d, bufs[i]); priv.elements[i].data = bufs[i]; i++; } if (rsa->p != NULL) { priv.elements[i].tag = TAG_RSA_PRIME1; priv.elements[i].length = BN_num_bytes(rsa->p); BN_bn2bin(rsa->p, bufs[i]); priv.elements[i].data = bufs[i]; i++; } if (rsa->q != NULL) { priv.elements[i].tag = TAG_RSA_PRIME2; priv.elements[i].length = BN_num_bytes(rsa->q); BN_bn2bin(rsa->q, bufs[i]); priv.elements[i].data = bufs[i]; i++; } if (rsa->dmp1 != NULL) { priv.elements[i].tag = TAG_RSA_EXPONENT1; priv.elements[i].length = BN_num_bytes(rsa->dmp1); BN_bn2bin(rsa->dmp1, bufs[i]); priv.elements[i].data = bufs[i]; i++; } if (rsa->dmq1 != NULL) { priv.elements[i].tag = TAG_RSA_EXPONENT2; priv.elements[i].length = BN_num_bytes(rsa->dmq1); BN_bn2bin(rsa->dmq1, bufs[i]); priv.elements[i].data = bufs[i]; i++; } if (rsa->iqmp != NULL) { priv.elements[i].tag = TAG_RSA_COEFFICIENT; priv.elements[i].length = BN_num_bytes(rsa->iqmp); BN_bn2bin(rsa->iqmp, bufs[i]); priv.elements[i].data = bufs[i]; i++; } if (key->engine != NULL) { priv.elements[i].tag = TAG_RSA_ENGINE; priv.elements[i].length = strlen(key->engine) + 1; priv.elements[i].data = (unsigned char *)key->engine; i++; } if (key->label != NULL) { priv.elements[i].tag = TAG_RSA_LABEL; priv.elements[i].length = strlen(key->label) + 1; priv.elements[i].data = (unsigned char *)key->label; i++; } priv.nelements = i; result = dst__privstruct_writefile(key, &priv, directory); fail: #if USE_EVP RSA_free(rsa); #endif for (i = 0; i < 8; i++) { if (bufs[i] == NULL) break; isc_mem_put(key->mctx, bufs[i], BN_num_bytes(rsa->n)); } return (result); } static isc_result_t rsa_check(RSA *rsa, RSA *pub) { /* Public parameters should be the same but if they are not set * copy them from the public key. */ if (pub != NULL) { if (rsa->n != NULL) { if (BN_cmp(rsa->n, pub->n) != 0) return (DST_R_INVALIDPRIVATEKEY); } else { rsa->n = pub->n; pub->n = NULL; } if (rsa->e != NULL) { if (BN_cmp(rsa->e, pub->e) != 0) return (DST_R_INVALIDPRIVATEKEY); } else { rsa->e = pub->e; pub->e = NULL; } } if (rsa->n == NULL || rsa->e == NULL) return (DST_R_INVALIDPRIVATEKEY); return (ISC_R_SUCCESS); } static isc_result_t opensslrsa_parse(dst_key_t *key, isc_lex_t *lexer, dst_key_t *pub) { dst_private_t priv; isc_result_t ret; int i; RSA *rsa = NULL, *pubrsa = NULL; #ifdef USE_ENGINE ENGINE *e = NULL; #endif isc_mem_t *mctx = key->mctx; const char *engine = NULL, *label = NULL; #if defined(USE_ENGINE) || USE_EVP EVP_PKEY *pkey = NULL; #endif #if USE_EVP if (pub != NULL && pub->keydata.pkey != NULL) pubrsa = EVP_PKEY_get1_RSA(pub->keydata.pkey); #else if (pub != NULL && pub->keydata.rsa != NULL) { pubrsa = pub->keydata.rsa; pub->keydata.rsa = NULL; } #endif /* read private key file */ ret = dst__privstruct_parse(key, DST_ALG_RSA, lexer, mctx, &priv); if (ret != ISC_R_SUCCESS) goto err; if (key->external && priv.nelements != 0) DST_RET(DST_R_INVALIDPRIVATEKEY); for (i = 0; i < priv.nelements; i++) { switch (priv.elements[i].tag) { case TAG_RSA_ENGINE: engine = (char *)priv.elements[i].data; break; case TAG_RSA_LABEL: label = (char *)priv.elements[i].data; break; default: break; } } /* * Is this key is stored in a HSM? * See if we can fetch it. */ if (label != NULL) { #ifdef USE_ENGINE if (engine == NULL) DST_RET(DST_R_NOENGINE); e = dst__openssl_getengine(engine); if (e == NULL) DST_RET(DST_R_NOENGINE); pkey = ENGINE_load_private_key(e, label, NULL, NULL); if (pkey == NULL) DST_RET(dst__openssl_toresult2( "ENGINE_load_private_key", ISC_R_NOTFOUND)); key->engine = isc_mem_strdup(key->mctx, engine); if (key->engine == NULL) DST_RET(ISC_R_NOMEMORY); key->label = isc_mem_strdup(key->mctx, label); if (key->label == NULL) DST_RET(ISC_R_NOMEMORY); rsa = EVP_PKEY_get1_RSA(pkey); if (rsa == NULL) DST_RET(dst__openssl_toresult(DST_R_OPENSSLFAILURE)); if (rsa_check(rsa, pubrsa) != ISC_R_SUCCESS) DST_RET(DST_R_INVALIDPRIVATEKEY); if (BN_num_bits(rsa->e) > RSA_MAX_PUBEXP_BITS) DST_RET(ISC_R_RANGE); if (pubrsa != NULL) RSA_free(pubrsa); key->key_size = EVP_PKEY_bits(pkey); #if USE_EVP key->keydata.pkey = pkey; RSA_free(rsa); #else key->keydata.rsa = rsa; EVP_PKEY_free(pkey); #endif dst__privstruct_free(&priv, mctx); memset(&priv, 0, sizeof(priv)); return (ISC_R_SUCCESS); #else DST_RET(DST_R_NOENGINE); #endif } rsa = RSA_new(); if (rsa == NULL) DST_RET(ISC_R_NOMEMORY); SET_FLAGS(rsa); #if USE_EVP pkey = EVP_PKEY_new(); if (pkey == NULL) DST_RET(ISC_R_NOMEMORY); if (!EVP_PKEY_set1_RSA(pkey, rsa)) DST_RET(ISC_R_FAILURE); key->keydata.pkey = pkey; #else key->keydata.rsa = rsa; #endif for (i = 0; i < priv.nelements; i++) { BIGNUM *bn; switch (priv.elements[i].tag) { case TAG_RSA_ENGINE: continue; case TAG_RSA_LABEL: continue; case TAG_RSA_PIN: continue; default: bn = BN_bin2bn(priv.elements[i].data, priv.elements[i].length, NULL); if (bn == NULL) DST_RET(ISC_R_NOMEMORY); } switch (priv.elements[i].tag) { case TAG_RSA_MODULUS: rsa->n = bn; break; case TAG_RSA_PUBLICEXPONENT: rsa->e = bn; break; case TAG_RSA_PRIVATEEXPONENT: rsa->d = bn; break; case TAG_RSA_PRIME1: rsa->p = bn; break; case TAG_RSA_PRIME2: rsa->q = bn; break; case TAG_RSA_EXPONENT1: rsa->dmp1 = bn; break; case TAG_RSA_EXPONENT2: rsa->dmq1 = bn; break; case TAG_RSA_COEFFICIENT: rsa->iqmp = bn; break; } } dst__privstruct_free(&priv, mctx); memset(&priv, 0, sizeof(priv)); if (rsa_check(rsa, pubrsa) != ISC_R_SUCCESS) DST_RET(DST_R_INVALIDPRIVATEKEY); if (!key->external) { if (BN_num_bits(rsa->e) > RSA_MAX_PUBEXP_BITS) DST_RET(ISC_R_RANGE); } key->key_size = BN_num_bits(rsa->n); if (pubrsa != NULL) RSA_free(pubrsa); #if USE_EVP RSA_free(rsa); #endif return (ISC_R_SUCCESS); err: #if USE_EVP if (pkey != NULL) EVP_PKEY_free(pkey); #endif if (rsa != NULL) RSA_free(rsa); if (pubrsa != NULL) RSA_free(pubrsa); key->keydata.generic = NULL; dst__privstruct_free(&priv, mctx); memset(&priv, 0, sizeof(priv)); return (ret); } static isc_result_t opensslrsa_fromlabel(dst_key_t *key, const char *engine, const char *label, const char *pin) { #ifdef USE_ENGINE ENGINE *e = NULL; isc_result_t ret; EVP_PKEY *pkey = NULL; RSA *rsa = NULL, *pubrsa = NULL; char *colon; UNUSED(pin); if (engine == NULL) DST_RET(DST_R_NOENGINE); e = dst__openssl_getengine(engine); if (e == NULL) DST_RET(DST_R_NOENGINE); pkey = ENGINE_load_public_key(e, label, NULL, NULL); if (pkey != NULL) { pubrsa = EVP_PKEY_get1_RSA(pkey); EVP_PKEY_free(pkey); if (pubrsa == NULL) DST_RET(dst__openssl_toresult(DST_R_OPENSSLFAILURE)); } pkey = ENGINE_load_private_key(e, label, NULL, NULL); if (pkey == NULL) DST_RET(dst__openssl_toresult2("ENGINE_load_private_key", ISC_R_NOTFOUND)); if (engine != NULL) { key->engine = isc_mem_strdup(key->mctx, engine); if (key->engine == NULL) DST_RET(ISC_R_NOMEMORY); } else { key->engine = isc_mem_strdup(key->mctx, label); if (key->engine == NULL) DST_RET(ISC_R_NOMEMORY); colon = strchr(key->engine, ':'); if (colon != NULL) *colon = '\0'; } key->label = isc_mem_strdup(key->mctx, label); if (key->label == NULL) DST_RET(ISC_R_NOMEMORY); rsa = EVP_PKEY_get1_RSA(pkey); if (rsa == NULL) DST_RET(dst__openssl_toresult(DST_R_OPENSSLFAILURE)); if (rsa_check(rsa, pubrsa) != ISC_R_SUCCESS) DST_RET(DST_R_INVALIDPRIVATEKEY); if (BN_num_bits(rsa->e) > RSA_MAX_PUBEXP_BITS) DST_RET(ISC_R_RANGE); if (pubrsa != NULL) RSA_free(pubrsa); key->key_size = EVP_PKEY_bits(pkey); #if USE_EVP key->keydata.pkey = pkey; RSA_free(rsa); #else key->keydata.rsa = rsa; EVP_PKEY_free(pkey); #endif return (ISC_R_SUCCESS); err: if (rsa != NULL) RSA_free(rsa); if (pubrsa != NULL) RSA_free(pubrsa); if (pkey != NULL) EVP_PKEY_free(pkey); return (ret); #else UNUSED(key); UNUSED(engine); UNUSED(label); UNUSED(pin); return(DST_R_NOENGINE); #endif } static dst_func_t opensslrsa_functions = { opensslrsa_createctx, opensslrsa_destroyctx, opensslrsa_adddata, opensslrsa_sign, opensslrsa_verify, opensslrsa_verify2, NULL, /*%< computesecret */ opensslrsa_compare, NULL, /*%< paramcompare */ opensslrsa_generate, opensslrsa_isprivate, opensslrsa_destroy, opensslrsa_todns, opensslrsa_fromdns, opensslrsa_tofile, opensslrsa_parse, NULL, /*%< cleanup */ opensslrsa_fromlabel, NULL, /*%< dump */ NULL, /*%< restore */ }; isc_result_t dst__opensslrsa_init(dst_func_t **funcp, unsigned char algorithm) { REQUIRE(funcp != NULL); if (*funcp == NULL) { switch (algorithm) { case DST_ALG_RSASHA256: #if defined(HAVE_EVP_SHA256) || !USE_EVP *funcp = &opensslrsa_functions; #endif break; case DST_ALG_RSASHA512: #if defined(HAVE_EVP_SHA512) || !USE_EVP *funcp = &opensslrsa_functions; #endif break; default: *funcp = &opensslrsa_functions; break; } } return (ISC_R_SUCCESS); } #else /* OPENSSL */ #include EMPTY_TRANSLATION_UNIT #endif /* OPENSSL */ /*! \file */ Index: stable/9/contrib/bind9/lib/dns/rdata/generic/openpgpkey_61.c =================================================================== --- stable/9/contrib/bind9/lib/dns/rdata/generic/openpgpkey_61.c (revision 287408) +++ stable/9/contrib/bind9/lib/dns/rdata/generic/openpgpkey_61.c (revision 287409) @@ -1,240 +1,242 @@ /* * Copyright (C) 2014 Internet Systems Consortium, Inc. ("ISC") * * Permission to use, copy, modify, and/or distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY * AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT, * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE * OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR * PERFORMANCE OF THIS SOFTWARE. */ #ifndef RDATA_GENERIC_OPENPGPKEY_61_C #define RDATA_GENERIC_OPENPGPKEY_61_C #define RRTYPE_OPENPGPKEY_ATTRIBUTES 0 static inline isc_result_t fromtext_openpgpkey(ARGS_FROMTEXT) { REQUIRE(type == 61); UNUSED(type); UNUSED(rdclass); UNUSED(callbacks); UNUSED(options); UNUSED(origin); /* * Keyring. */ return (isc_base64_tobuffer(lexer, target, -1)); } static inline isc_result_t totext_openpgpkey(ARGS_TOTEXT) { isc_region_t sr; REQUIRE(rdata->type == 61); REQUIRE(rdata->length != 0); dns_rdata_toregion(rdata, &sr); /* * Keyring */ if ((tctx->flags & DNS_STYLEFLAG_MULTILINE) != 0) RETERR(str_totext("( ", target)); if (tctx->width == 0) /* No splitting */ RETERR(isc_base64_totext(&sr, 60, "", target)); else RETERR(isc_base64_totext(&sr, tctx->width - 2, tctx->linebreak, target)); if ((tctx->flags & DNS_STYLEFLAG_MULTILINE) != 0) RETERR(str_totext(" )", target)); return (ISC_R_SUCCESS); } static inline isc_result_t fromwire_openpgpkey(ARGS_FROMWIRE) { isc_region_t sr; REQUIRE(type == 61); UNUSED(type); UNUSED(rdclass); UNUSED(dctx); UNUSED(options); /* * Keyring. */ isc_buffer_activeregion(source, &sr); + if (sr.length < 1) + return (ISC_R_UNEXPECTEDEND); isc_buffer_forward(source, sr.length); return (mem_tobuffer(target, sr.base, sr.length)); } static inline isc_result_t towire_openpgpkey(ARGS_TOWIRE) { isc_region_t sr; REQUIRE(rdata->type == 61); REQUIRE(rdata->length != 0); UNUSED(cctx); dns_rdata_toregion(rdata, &sr); return (mem_tobuffer(target, sr.base, sr.length)); } static inline int compare_openpgpkey(ARGS_COMPARE) { isc_region_t r1; isc_region_t r2; REQUIRE(rdata1->type == rdata2->type); REQUIRE(rdata1->rdclass == rdata2->rdclass); REQUIRE(rdata1->type == 61); REQUIRE(rdata1->length != 0); REQUIRE(rdata2->length != 0); dns_rdata_toregion(rdata1, &r1); dns_rdata_toregion(rdata2, &r2); return (isc_region_compare(&r1, &r2)); } static inline isc_result_t fromstruct_openpgpkey(ARGS_FROMSTRUCT) { dns_rdata_openpgpkey_t *sig = source; REQUIRE(type == 61); REQUIRE(source != NULL); REQUIRE(sig->common.rdtype == type); REQUIRE(sig->common.rdclass == rdclass); REQUIRE(sig->keyring != NULL && sig->length != 0); UNUSED(type); UNUSED(rdclass); /* * Keyring. */ return (mem_tobuffer(target, sig->keyring, sig->length)); } static inline isc_result_t tostruct_openpgpkey(ARGS_TOSTRUCT) { isc_region_t sr; dns_rdata_openpgpkey_t *sig = target; REQUIRE(rdata->type == 61); REQUIRE(target != NULL); REQUIRE(rdata->length != 0); sig->common.rdclass = rdata->rdclass; sig->common.rdtype = rdata->type; ISC_LINK_INIT(&sig->common, link); dns_rdata_toregion(rdata, &sr); /* * Keyring. */ sig->length = sr.length; sig->keyring = mem_maybedup(mctx, sr.base, sig->length); if (sig->keyring == NULL) goto cleanup; sig->mctx = mctx; return (ISC_R_SUCCESS); cleanup: return (ISC_R_NOMEMORY); } static inline void freestruct_openpgpkey(ARGS_FREESTRUCT) { dns_rdata_openpgpkey_t *sig = (dns_rdata_openpgpkey_t *) source; REQUIRE(source != NULL); REQUIRE(sig->common.rdtype == 61); if (sig->mctx == NULL) return; if (sig->keyring != NULL) isc_mem_free(sig->mctx, sig->keyring); sig->mctx = NULL; } static inline isc_result_t additionaldata_openpgpkey(ARGS_ADDLDATA) { REQUIRE(rdata->type == 61); UNUSED(rdata); UNUSED(add); UNUSED(arg); return (ISC_R_SUCCESS); } static inline isc_result_t digest_openpgpkey(ARGS_DIGEST) { isc_region_t r; REQUIRE(rdata->type == 61); dns_rdata_toregion(rdata, &r); return ((digest)(arg, &r)); } static inline isc_boolean_t checkowner_openpgpkey(ARGS_CHECKOWNER) { REQUIRE(type == 61); UNUSED(name); UNUSED(type); UNUSED(rdclass); UNUSED(wildcard); return (ISC_TRUE); } static inline isc_boolean_t checknames_openpgpkey(ARGS_CHECKNAMES) { REQUIRE(rdata->type == 61); UNUSED(rdata); UNUSED(owner); UNUSED(bad); return (ISC_TRUE); } static inline int casecompare_openpgpkey(ARGS_COMPARE) { isc_region_t r1; isc_region_t r2; REQUIRE(rdata1->type == rdata2->type); REQUIRE(rdata1->rdclass == rdata2->rdclass); REQUIRE(rdata1->type == 61); REQUIRE(rdata1->length != 0); REQUIRE(rdata2->length != 0); dns_rdata_toregion(rdata1, &r1); dns_rdata_toregion(rdata2, &r2); return (isc_region_compare(&r1, &r2)); } #endif /* RDATA_GENERIC_OPENPGPKEY_61_C */ Index: stable/9/contrib/bind9/lib/dns/resolver.c =================================================================== --- stable/9/contrib/bind9/lib/dns/resolver.c (revision 287408) +++ stable/9/contrib/bind9/lib/dns/resolver.c (revision 287409) @@ -1,9248 +1,9255 @@ /* * Copyright (C) 2004-2014 Internet Systems Consortium, Inc. ("ISC") * Copyright (C) 1999-2003 Internet Software Consortium. * * Permission to use, copy, modify, and/or distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY * AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT, * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE * OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR * PERFORMANCE OF THIS SOFTWARE. */ /* $Id$ */ /*! \file */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define DNS_RESOLVER_TRACE #ifdef DNS_RESOLVER_TRACE #define RTRACE(m) isc_log_write(dns_lctx, \ DNS_LOGCATEGORY_RESOLVER, \ DNS_LOGMODULE_RESOLVER, \ ISC_LOG_DEBUG(3), \ "res %p: %s", res, (m)) #define RRTRACE(r, m) isc_log_write(dns_lctx, \ DNS_LOGCATEGORY_RESOLVER, \ DNS_LOGMODULE_RESOLVER, \ ISC_LOG_DEBUG(3), \ "res %p: %s", (r), (m)) #define FCTXTRACE(m) isc_log_write(dns_lctx, \ DNS_LOGCATEGORY_RESOLVER, \ DNS_LOGMODULE_RESOLVER, \ ISC_LOG_DEBUG(3), \ "fctx %p(%s): %s", fctx, fctx->info, (m)) #define FCTXTRACE2(m1, m2) \ isc_log_write(dns_lctx, \ DNS_LOGCATEGORY_RESOLVER, \ DNS_LOGMODULE_RESOLVER, \ ISC_LOG_DEBUG(3), \ "fctx %p(%s): %s %s", \ fctx, fctx->info, (m1), (m2)) #define FTRACE(m) isc_log_write(dns_lctx, \ DNS_LOGCATEGORY_RESOLVER, \ DNS_LOGMODULE_RESOLVER, \ ISC_LOG_DEBUG(3), \ "fetch %p (fctx %p(%s)): %s", \ fetch, fetch->private, \ fetch->private->info, (m)) #define QTRACE(m) isc_log_write(dns_lctx, \ DNS_LOGCATEGORY_RESOLVER, \ DNS_LOGMODULE_RESOLVER, \ ISC_LOG_DEBUG(3), \ "resquery %p (fctx %p(%s)): %s", \ query, query->fctx, \ query->fctx->info, (m)) #else #define RTRACE(m) #define RRTRACE(r, m) #define FCTXTRACE(m) #define FCTXTRACE2(m1, m2) #define FTRACE(m) #define QTRACE(m) #endif #define US_PER_SEC 1000000U /* * The maximum time we will wait for a single query. */ #define MAX_SINGLE_QUERY_TIMEOUT 9U #define MAX_SINGLE_QUERY_TIMEOUT_US (MAX_SINGLE_QUERY_TIMEOUT*US_PER_SEC) /* * We need to allow a individual query time to complete / timeout. */ #define MINIMUM_QUERY_TIMEOUT (MAX_SINGLE_QUERY_TIMEOUT + 1U) /* The default time in seconds for the whole query to live. */ #ifndef DEFAULT_QUERY_TIMEOUT #define DEFAULT_QUERY_TIMEOUT MINIMUM_QUERY_TIMEOUT #endif #ifndef MAXIMUM_QUERY_TIMEOUT #define MAXIMUM_QUERY_TIMEOUT 30 /* The maximum time in seconds for the whole query to live. */ #endif /* The default maximum number of recursions to follow before giving up. */ #ifndef DEFAULT_RECURSION_DEPTH #define DEFAULT_RECURSION_DEPTH 7 #endif /* The default maximum number of iterative queries to allow before giving up. */ #ifndef DEFAULT_MAX_QUERIES #define DEFAULT_MAX_QUERIES 50 #endif /*% * Maximum EDNS0 input packet size. */ #define RECV_BUFFER_SIZE 4096 /* XXXRTH Constant. */ /*% * This defines the maximum number of timeouts we will permit before we * disable EDNS0 on the query. */ #define MAX_EDNS0_TIMEOUTS 3 typedef struct fetchctx fetchctx_t; typedef struct query { /* Locked by task event serialization. */ unsigned int magic; fetchctx_t * fctx; isc_mem_t * mctx; dns_dispatchmgr_t * dispatchmgr; dns_dispatch_t * dispatch; isc_boolean_t exclusivesocket; dns_adbaddrinfo_t * addrinfo; isc_socket_t * tcpsocket; isc_time_t start; dns_messageid_t id; dns_dispentry_t * dispentry; ISC_LINK(struct query) link; isc_buffer_t buffer; isc_buffer_t *tsig; dns_tsigkey_t *tsigkey; isc_socketevent_t sendevent; int ednsversion; unsigned int options; unsigned int attributes; unsigned int sends; unsigned int connects; unsigned char data[512]; } resquery_t; #define QUERY_MAGIC ISC_MAGIC('Q', '!', '!', '!') #define VALID_QUERY(query) ISC_MAGIC_VALID(query, QUERY_MAGIC) #define RESQUERY_ATTR_CANCELED 0x02 #define RESQUERY_CONNECTING(q) ((q)->connects > 0) #define RESQUERY_CANCELED(q) (((q)->attributes & \ RESQUERY_ATTR_CANCELED) != 0) #define RESQUERY_SENDING(q) ((q)->sends > 0) typedef enum { fetchstate_init = 0, /*%< Start event has not run yet. */ fetchstate_active, fetchstate_done /*%< FETCHDONE events posted. */ } fetchstate; typedef enum { badns_unreachable = 0, badns_response, badns_validation } badnstype_t; struct fetchctx { /*% Not locked. */ unsigned int magic; dns_resolver_t * res; dns_name_t name; dns_rdatatype_t type; unsigned int options; unsigned int bucketnum; char * info; isc_mem_t * mctx; /*% Locked by appropriate bucket lock. */ fetchstate state; isc_boolean_t want_shutdown; isc_boolean_t cloned; isc_boolean_t spilled; unsigned int references; isc_event_t control_event; ISC_LINK(struct fetchctx) link; ISC_LIST(dns_fetchevent_t) events; /*% Locked by task event serialization. */ dns_name_t domain; dns_rdataset_t nameservers; unsigned int attributes; isc_timer_t * timer; isc_time_t expires; isc_interval_t interval; dns_message_t * qmessage; dns_message_t * rmessage; ISC_LIST(resquery_t) queries; dns_adbfindlist_t finds; dns_adbfind_t * find; dns_adbfindlist_t altfinds; dns_adbfind_t * altfind; dns_adbaddrinfolist_t forwaddrs; dns_adbaddrinfolist_t altaddrs; isc_sockaddrlist_t forwarders; dns_fwdpolicy_t fwdpolicy; isc_sockaddrlist_t bad; isc_sockaddrlist_t edns; isc_sockaddrlist_t edns512; isc_sockaddrlist_t bad_edns; dns_validator_t * validator; ISC_LIST(dns_validator_t) validators; dns_db_t * cache; dns_adb_t * adb; isc_boolean_t ns_ttl_ok; isc_uint32_t ns_ttl; isc_counter_t * qc; /*% * The number of events we're waiting for. */ unsigned int pending; /*% * The number of times we've "restarted" the current * nameserver set. This acts as a failsafe to prevent * us from pounding constantly on a particular set of * servers that, for whatever reason, are not giving * us useful responses, but are responding in such a * way that they are not marked "bad". */ unsigned int restarts; /*% * The number of timeouts that have occurred since we * last successfully received a response packet. This * is used for EDNS0 black hole detection. */ unsigned int timeouts; /*% * Look aside state for DS lookups. */ dns_name_t nsname; dns_fetch_t * nsfetch; dns_rdataset_t nsrrset; /*% * Number of queries that reference this context. */ unsigned int nqueries; /*% * The reason to print when logging a successful * response to a query. */ const char * reason; /*% * Random numbers to use for mixing up server addresses. */ isc_uint32_t rand_buf; isc_uint32_t rand_bits; /*% * Fetch-local statistics for detailed logging. */ isc_result_t result; /*%< fetch result */ isc_result_t vresult; /*%< validation result */ int exitline; isc_time_t start; isc_uint64_t duration; isc_boolean_t logged; unsigned int querysent; unsigned int referrals; unsigned int lamecount; unsigned int neterr; unsigned int badresp; unsigned int adberr; unsigned int findfail; unsigned int valfail; isc_boolean_t timeout; dns_adbaddrinfo_t *addrinfo; isc_sockaddr_t *client; unsigned int depth; }; #define FCTX_MAGIC ISC_MAGIC('F', '!', '!', '!') #define VALID_FCTX(fctx) ISC_MAGIC_VALID(fctx, FCTX_MAGIC) #define FCTX_ATTR_HAVEANSWER 0x0001 #define FCTX_ATTR_GLUING 0x0002 #define FCTX_ATTR_ADDRWAIT 0x0004 #define FCTX_ATTR_SHUTTINGDOWN 0x0008 #define FCTX_ATTR_WANTCACHE 0x0010 #define FCTX_ATTR_WANTNCACHE 0x0020 #define FCTX_ATTR_NEEDEDNS0 0x0040 #define FCTX_ATTR_TRIEDFIND 0x0080 #define FCTX_ATTR_TRIEDALT 0x0100 #define HAVE_ANSWER(f) (((f)->attributes & FCTX_ATTR_HAVEANSWER) != \ 0) #define GLUING(f) (((f)->attributes & FCTX_ATTR_GLUING) != \ 0) #define ADDRWAIT(f) (((f)->attributes & FCTX_ATTR_ADDRWAIT) != \ 0) #define SHUTTINGDOWN(f) (((f)->attributes & FCTX_ATTR_SHUTTINGDOWN) \ != 0) #define WANTCACHE(f) (((f)->attributes & FCTX_ATTR_WANTCACHE) != 0) #define WANTNCACHE(f) (((f)->attributes & FCTX_ATTR_WANTNCACHE) != 0) #define NEEDEDNS0(f) (((f)->attributes & FCTX_ATTR_NEEDEDNS0) != 0) #define TRIEDFIND(f) (((f)->attributes & FCTX_ATTR_TRIEDFIND) != 0) #define TRIEDALT(f) (((f)->attributes & FCTX_ATTR_TRIEDALT) != 0) typedef struct { dns_adbaddrinfo_t * addrinfo; fetchctx_t * fctx; } dns_valarg_t; struct dns_fetch { unsigned int magic; isc_mem_t * mctx; fetchctx_t * private; }; #define DNS_FETCH_MAGIC ISC_MAGIC('F', 't', 'c', 'h') #define DNS_FETCH_VALID(fetch) ISC_MAGIC_VALID(fetch, DNS_FETCH_MAGIC) typedef struct fctxbucket { isc_task_t * task; isc_mutex_t lock; ISC_LIST(fetchctx_t) fctxs; isc_boolean_t exiting; isc_mem_t * mctx; } fctxbucket_t; typedef struct alternate { isc_boolean_t isaddress; union { isc_sockaddr_t addr; struct { dns_name_t name; in_port_t port; } _n; } _u; ISC_LINK(struct alternate) link; } alternate_t; typedef struct dns_badcache dns_badcache_t; struct dns_badcache { dns_badcache_t * next; dns_rdatatype_t type; isc_time_t expire; unsigned int hashval; dns_name_t name; }; #define DNS_BADCACHE_SIZE 1021 #define DNS_BADCACHE_TTL(fctx) \ (((fctx)->res->lame_ttl > 30 ) ? (fctx)->res->lame_ttl : 30) struct dns_resolver { /* Unlocked. */ unsigned int magic; isc_mem_t * mctx; isc_mutex_t lock; isc_mutex_t nlock; isc_mutex_t primelock; dns_rdataclass_t rdclass; isc_socketmgr_t * socketmgr; isc_timermgr_t * timermgr; isc_taskmgr_t * taskmgr; dns_view_t * view; isc_boolean_t frozen; unsigned int options; dns_dispatchmgr_t * dispatchmgr; dns_dispatchset_t * dispatches4; isc_boolean_t exclusivev4; dns_dispatchset_t * dispatches6; isc_boolean_t exclusivev6; unsigned int nbuckets; fctxbucket_t * buckets; isc_uint32_t lame_ttl; ISC_LIST(alternate_t) alternates; isc_uint16_t udpsize; #if USE_ALGLOCK isc_rwlock_t alglock; #endif dns_rbt_t * algorithms; #if USE_MBSLOCK isc_rwlock_t mbslock; #endif dns_rbt_t * mustbesecure; unsigned int spillatmax; unsigned int spillatmin; isc_timer_t * spillattimer; isc_boolean_t zero_no_soa_ttl; unsigned int query_timeout; unsigned int maxdepth; unsigned int maxqueries; /* Locked by lock. */ unsigned int references; isc_boolean_t exiting; isc_eventlist_t whenshutdown; unsigned int activebuckets; isc_boolean_t priming; unsigned int spillat; /* clients-per-query */ /* Bad cache. */ dns_badcache_t ** badcache; unsigned int badcount; unsigned int badhash; unsigned int badsweep; /* Locked by primelock. */ dns_fetch_t * primefetch; /* Locked by nlock. */ unsigned int nfctx; }; #define RES_MAGIC ISC_MAGIC('R', 'e', 's', '!') #define VALID_RESOLVER(res) ISC_MAGIC_VALID(res, RES_MAGIC) /*% * Private addrinfo flags. These must not conflict with DNS_FETCHOPT_NOEDNS0, * which we also use as an addrinfo flag. */ #define FCTX_ADDRINFO_MARK 0x0001 #define FCTX_ADDRINFO_FORWARDER 0x1000 #define FCTX_ADDRINFO_TRIED 0x2000 #define FCTX_ADDRINFO_EDNSOK 0x4000 #define UNMARKED(a) (((a)->flags & FCTX_ADDRINFO_MARK) \ == 0) #define ISFORWARDER(a) (((a)->flags & \ FCTX_ADDRINFO_FORWARDER) != 0) #define TRIED(a) (((a)->flags & \ FCTX_ADDRINFO_TRIED) != 0) #define EDNSOK(a) (((a)->flags & \ FCTX_ADDRINFO_EDNSOK) != 0) #define NXDOMAIN(r) (((r)->attributes & DNS_RDATASETATTR_NXDOMAIN) != 0) #define NEGATIVE(r) (((r)->attributes & DNS_RDATASETATTR_NEGATIVE) != 0) static void destroy(dns_resolver_t *res); static void empty_bucket(dns_resolver_t *res); static isc_result_t resquery_send(resquery_t *query); static void resquery_response(isc_task_t *task, isc_event_t *event); static void resquery_connected(isc_task_t *task, isc_event_t *event); static void fctx_try(fetchctx_t *fctx, isc_boolean_t retrying, isc_boolean_t badcache); static void fctx_destroy(fetchctx_t *fctx); static isc_boolean_t fctx_unlink(fetchctx_t *fctx); static isc_result_t ncache_adderesult(dns_message_t *message, dns_db_t *cache, dns_dbnode_t *node, dns_rdatatype_t covers, isc_stdtime_t now, dns_ttl_t maxttl, isc_boolean_t optout, isc_boolean_t secure, dns_rdataset_t *ardataset, isc_result_t *eresultp); static void validated(isc_task_t *task, isc_event_t *event); static isc_boolean_t maybe_destroy(fetchctx_t *fctx, isc_boolean_t locked); static void add_bad(fetchctx_t *fctx, dns_adbaddrinfo_t *addrinfo, isc_result_t reason, badnstype_t badtype); static inline isc_result_t findnoqname(fetchctx_t *fctx, dns_name_t *name, dns_rdatatype_t type, dns_name_t **noqname); /*% * Increment resolver-related statistics counters. */ static inline void inc_stats(dns_resolver_t *res, isc_statscounter_t counter) { if (res->view->resstats != NULL) isc_stats_increment(res->view->resstats, counter); } static isc_result_t valcreate(fetchctx_t *fctx, dns_adbaddrinfo_t *addrinfo, dns_name_t *name, dns_rdatatype_t type, dns_rdataset_t *rdataset, dns_rdataset_t *sigrdataset, unsigned int valoptions, isc_task_t *task) { dns_validator_t *validator = NULL; dns_valarg_t *valarg; isc_result_t result; valarg = isc_mem_get(fctx->mctx, sizeof(*valarg)); if (valarg == NULL) return (ISC_R_NOMEMORY); valarg->fctx = fctx; valarg->addrinfo = addrinfo; if (!ISC_LIST_EMPTY(fctx->validators)) INSIST((valoptions & DNS_VALIDATOR_DEFER) != 0); result = dns_validator_create(fctx->res->view, name, type, rdataset, sigrdataset, fctx->rmessage, valoptions, task, validated, valarg, &validator); if (result == ISC_R_SUCCESS) { inc_stats(fctx->res, dns_resstatscounter_val); if ((valoptions & DNS_VALIDATOR_DEFER) == 0) { INSIST(fctx->validator == NULL); fctx->validator = validator; } ISC_LIST_APPEND(fctx->validators, validator, link); } else isc_mem_put(fctx->mctx, valarg, sizeof(*valarg)); return (result); } static isc_boolean_t rrsig_fromchildzone(fetchctx_t *fctx, dns_rdataset_t *rdataset) { dns_namereln_t namereln; dns_rdata_rrsig_t rrsig; dns_rdata_t rdata = DNS_RDATA_INIT; int order; isc_result_t result; unsigned int labels; for (result = dns_rdataset_first(rdataset); result == ISC_R_SUCCESS; result = dns_rdataset_next(rdataset)) { dns_rdataset_current(rdataset, &rdata); result = dns_rdata_tostruct(&rdata, &rrsig, NULL); RUNTIME_CHECK(result == ISC_R_SUCCESS); namereln = dns_name_fullcompare(&rrsig.signer, &fctx->domain, &order, &labels); if (namereln == dns_namereln_subdomain) return (ISC_TRUE); dns_rdata_reset(&rdata); } return (ISC_FALSE); } static isc_boolean_t fix_mustbedelegationornxdomain(dns_message_t *message, fetchctx_t *fctx) { dns_name_t *name; dns_name_t *domain = &fctx->domain; dns_rdataset_t *rdataset; dns_rdatatype_t type; isc_result_t result; isc_boolean_t keep_auth = ISC_FALSE; if (message->rcode == dns_rcode_nxdomain) return (ISC_FALSE); /* * A DS RRset can appear anywhere in a zone, even for a delegation-only * zone. So a response to an explicit query for this type should be * excluded from delegation-only fixup. * * SOA, NS, and DNSKEY can only exist at a zone apex, so a postive * response to a query for these types can never violate the * delegation-only assumption: if the query name is below a * zone cut, the response should normally be a referral, which should * be accepted; if the query name is below a zone cut but the server * happens to have authority for the zone of the query name, the * response is a (non-referral) answer. But this does not violate * delegation-only because the query name must be in a different zone * due to the "apex-only" nature of these types. Note that if the * remote server happens to have authority for a child zone of a * delegation-only zone, we may still incorrectly "fix" the response * with NXDOMAIN for queries for other types. Unfortunately it's * generally impossible to differentiate this case from violation of * the delegation-only assumption. Once the resolver learns the * correct zone cut, possibly via a separate query for an "apex-only" * type, queries for other types will be resolved correctly. * * A query for type ANY will be accepted if it hits an exceptional * type above in the answer section as it should be from a child * zone. * * Also accept answers with RRSIG records from the child zone. * Direct queries for RRSIG records should not be answered from * the parent zone. */ if (message->counts[DNS_SECTION_ANSWER] != 0 && (fctx->type == dns_rdatatype_ns || fctx->type == dns_rdatatype_ds || fctx->type == dns_rdatatype_soa || fctx->type == dns_rdatatype_any || fctx->type == dns_rdatatype_rrsig || fctx->type == dns_rdatatype_dnskey)) { result = dns_message_firstname(message, DNS_SECTION_ANSWER); while (result == ISC_R_SUCCESS) { name = NULL; dns_message_currentname(message, DNS_SECTION_ANSWER, &name); for (rdataset = ISC_LIST_HEAD(name->list); rdataset != NULL; rdataset = ISC_LIST_NEXT(rdataset, link)) { if (!dns_name_equal(name, &fctx->name)) continue; type = rdataset->type; /* * RRsig from child? */ if (type == dns_rdatatype_rrsig && rrsig_fromchildzone(fctx, rdataset)) return (ISC_FALSE); /* * Direct query for apex records or DS. */ if (fctx->type == type && (type == dns_rdatatype_ds || type == dns_rdatatype_ns || type == dns_rdatatype_soa || type == dns_rdatatype_dnskey)) return (ISC_FALSE); /* * Indirect query for apex records or DS. */ if (fctx->type == dns_rdatatype_any && (type == dns_rdatatype_ns || type == dns_rdatatype_ds || type == dns_rdatatype_soa || type == dns_rdatatype_dnskey)) return (ISC_FALSE); } result = dns_message_nextname(message, DNS_SECTION_ANSWER); } } /* * A NODATA response to a DS query? */ if (fctx->type == dns_rdatatype_ds && message->counts[DNS_SECTION_ANSWER] == 0) return (ISC_FALSE); /* Look for referral or indication of answer from child zone? */ if (message->counts[DNS_SECTION_AUTHORITY] == 0) goto munge; result = dns_message_firstname(message, DNS_SECTION_AUTHORITY); while (result == ISC_R_SUCCESS) { name = NULL; dns_message_currentname(message, DNS_SECTION_AUTHORITY, &name); for (rdataset = ISC_LIST_HEAD(name->list); rdataset != NULL; rdataset = ISC_LIST_NEXT(rdataset, link)) { type = rdataset->type; if (type == dns_rdatatype_soa && dns_name_equal(name, domain)) keep_auth = ISC_TRUE; if (type != dns_rdatatype_ns && type != dns_rdatatype_soa && type != dns_rdatatype_rrsig) continue; if (type == dns_rdatatype_rrsig) { if (rrsig_fromchildzone(fctx, rdataset)) return (ISC_FALSE); else continue; } /* NS or SOA records. */ if (dns_name_equal(name, domain)) { /* * If a query for ANY causes a negative * response, we can be sure that this is * an empty node. For other type of queries * we cannot differentiate an empty node * from a node that just doesn't have that * type of record. We only accept the former * case. */ if (message->counts[DNS_SECTION_ANSWER] == 0 && fctx->type == dns_rdatatype_any) return (ISC_FALSE); } else if (dns_name_issubdomain(name, domain)) { /* Referral or answer from child zone. */ return (ISC_FALSE); } } result = dns_message_nextname(message, DNS_SECTION_AUTHORITY); } munge: message->rcode = dns_rcode_nxdomain; message->counts[DNS_SECTION_ANSWER] = 0; if (!keep_auth) message->counts[DNS_SECTION_AUTHORITY] = 0; message->counts[DNS_SECTION_ADDITIONAL] = 0; return (ISC_TRUE); } static inline isc_result_t fctx_starttimer(fetchctx_t *fctx) { /* * Start the lifetime timer for fctx. * * This is also used for stopping the idle timer; in that * case we must purge events already posted to ensure that * no further idle events are delivered. */ return (isc_timer_reset(fctx->timer, isc_timertype_once, &fctx->expires, NULL, ISC_TRUE)); } static inline void fctx_stoptimer(fetchctx_t *fctx) { isc_result_t result; /* * We don't return a result if resetting the timer to inactive fails * since there's nothing to be done about it. Resetting to inactive * should never fail anyway, since the code as currently written * cannot fail in that case. */ result = isc_timer_reset(fctx->timer, isc_timertype_inactive, NULL, NULL, ISC_TRUE); if (result != ISC_R_SUCCESS) { UNEXPECTED_ERROR(__FILE__, __LINE__, "isc_timer_reset(): %s", isc_result_totext(result)); } } static inline isc_result_t fctx_startidletimer(fetchctx_t *fctx, isc_interval_t *interval) { /* * Start the idle timer for fctx. The lifetime timer continues * to be in effect. */ return (isc_timer_reset(fctx->timer, isc_timertype_once, &fctx->expires, interval, ISC_FALSE)); } /* * Stopping the idle timer is equivalent to calling fctx_starttimer(), but * we use fctx_stopidletimer for readability in the code below. */ #define fctx_stopidletimer fctx_starttimer static inline void resquery_destroy(resquery_t **queryp) { resquery_t *query; REQUIRE(queryp != NULL); query = *queryp; REQUIRE(!ISC_LINK_LINKED(query, link)); INSIST(query->tcpsocket == NULL); query->fctx->nqueries--; if (SHUTTINGDOWN(query->fctx)) { dns_resolver_t *res = query->fctx->res; if (maybe_destroy(query->fctx, ISC_FALSE)) empty_bucket(res); } query->magic = 0; isc_mem_put(query->mctx, query, sizeof(*query)); *queryp = NULL; } static void fctx_cancelquery(resquery_t **queryp, dns_dispatchevent_t **deventp, isc_time_t *finish, isc_boolean_t no_response) { fetchctx_t *fctx; resquery_t *query; unsigned int rtt, rttms; unsigned int factor; dns_adbfind_t *find; dns_adbaddrinfo_t *addrinfo; isc_socket_t *socket; isc_stdtime_t now; query = *queryp; fctx = query->fctx; FCTXTRACE("cancelquery"); REQUIRE(!RESQUERY_CANCELED(query)); query->attributes |= RESQUERY_ATTR_CANCELED; /* * Should we update the RTT? */ if (finish != NULL || no_response) { if (finish != NULL) { /* * We have both the start and finish times for this * packet, so we can compute a real RTT. */ rtt = (unsigned int)isc_time_microdiff(finish, &query->start); factor = DNS_ADB_RTTADJDEFAULT; rttms = rtt / 1000; if (rttms < DNS_RESOLVER_QRYRTTCLASS0) { inc_stats(fctx->res, dns_resstatscounter_queryrtt0); } else if (rttms < DNS_RESOLVER_QRYRTTCLASS1) { inc_stats(fctx->res, dns_resstatscounter_queryrtt1); } else if (rttms < DNS_RESOLVER_QRYRTTCLASS2) { inc_stats(fctx->res, dns_resstatscounter_queryrtt2); } else if (rttms < DNS_RESOLVER_QRYRTTCLASS3) { inc_stats(fctx->res, dns_resstatscounter_queryrtt3); } else if (rttms < DNS_RESOLVER_QRYRTTCLASS4) { inc_stats(fctx->res, dns_resstatscounter_queryrtt4); } else { inc_stats(fctx->res, dns_resstatscounter_queryrtt5); } } else { /* * We don't have an RTT for this query. Maybe the * packet was lost, or maybe this server is very * slow. We don't know. Increase the RTT. */ INSIST(no_response); rtt = query->addrinfo->srtt + 200000; if (rtt > MAX_SINGLE_QUERY_TIMEOUT_US) rtt = MAX_SINGLE_QUERY_TIMEOUT_US; /* * Replace the current RTT with our value. */ factor = DNS_ADB_RTTADJREPLACE; } dns_adb_adjustsrtt(fctx->adb, query->addrinfo, rtt, factor); } /* Remember that the server has been tried. */ if (!TRIED(query->addrinfo)) { dns_adb_changeflags(fctx->adb, query->addrinfo, FCTX_ADDRINFO_TRIED, FCTX_ADDRINFO_TRIED); } /* * Age RTTs of servers not tried. */ isc_stdtime_get(&now); if (finish != NULL) for (addrinfo = ISC_LIST_HEAD(fctx->forwaddrs); addrinfo != NULL; addrinfo = ISC_LIST_NEXT(addrinfo, publink)) if (UNMARKED(addrinfo)) dns_adb_agesrtt(fctx->adb, addrinfo, now); if (finish != NULL && TRIEDFIND(fctx)) for (find = ISC_LIST_HEAD(fctx->finds); find != NULL; find = ISC_LIST_NEXT(find, publink)) for (addrinfo = ISC_LIST_HEAD(find->list); addrinfo != NULL; addrinfo = ISC_LIST_NEXT(addrinfo, publink)) if (UNMARKED(addrinfo)) dns_adb_agesrtt(fctx->adb, addrinfo, now); if (finish != NULL && TRIEDALT(fctx)) { for (addrinfo = ISC_LIST_HEAD(fctx->altaddrs); addrinfo != NULL; addrinfo = ISC_LIST_NEXT(addrinfo, publink)) if (UNMARKED(addrinfo)) dns_adb_agesrtt(fctx->adb, addrinfo, now); for (find = ISC_LIST_HEAD(fctx->altfinds); find != NULL; find = ISC_LIST_NEXT(find, publink)) for (addrinfo = ISC_LIST_HEAD(find->list); addrinfo != NULL; addrinfo = ISC_LIST_NEXT(addrinfo, publink)) if (UNMARKED(addrinfo)) dns_adb_agesrtt(fctx->adb, addrinfo, now); } /* * Check for any outstanding socket events. If they exist, cancel * them and let the event handlers finish the cleanup. The resolver * only needs to worry about managing the connect and send events; * the dispatcher manages the recv events. */ if (RESQUERY_CONNECTING(query)) { /* * Cancel the connect. */ if (query->tcpsocket != NULL) { isc_socket_cancel(query->tcpsocket, NULL, ISC_SOCKCANCEL_CONNECT); } else if (query->dispentry != NULL) { INSIST(query->exclusivesocket); socket = dns_dispatch_getentrysocket(query->dispentry); if (socket != NULL) isc_socket_cancel(socket, NULL, ISC_SOCKCANCEL_CONNECT); } } else if (RESQUERY_SENDING(query)) { /* * Cancel the pending send. */ if (query->exclusivesocket && query->dispentry != NULL) socket = dns_dispatch_getentrysocket(query->dispentry); else socket = dns_dispatch_getsocket(query->dispatch); if (socket != NULL) isc_socket_cancel(socket, NULL, ISC_SOCKCANCEL_SEND); } if (query->dispentry != NULL) dns_dispatch_removeresponse(&query->dispentry, deventp); ISC_LIST_UNLINK(fctx->queries, query, link); if (query->tsig != NULL) isc_buffer_free(&query->tsig); if (query->tsigkey != NULL) dns_tsigkey_detach(&query->tsigkey); if (query->dispatch != NULL) dns_dispatch_detach(&query->dispatch); if (! (RESQUERY_CONNECTING(query) || RESQUERY_SENDING(query))) /* * It's safe to destroy the query now. */ resquery_destroy(&query); } static void fctx_cancelqueries(fetchctx_t *fctx, isc_boolean_t no_response) { resquery_t *query, *next_query; FCTXTRACE("cancelqueries"); for (query = ISC_LIST_HEAD(fctx->queries); query != NULL; query = next_query) { next_query = ISC_LIST_NEXT(query, link); fctx_cancelquery(&query, NULL, NULL, no_response); } } static void fctx_cleanupfinds(fetchctx_t *fctx) { dns_adbfind_t *find, *next_find; REQUIRE(ISC_LIST_EMPTY(fctx->queries)); for (find = ISC_LIST_HEAD(fctx->finds); find != NULL; find = next_find) { next_find = ISC_LIST_NEXT(find, publink); ISC_LIST_UNLINK(fctx->finds, find, publink); dns_adb_destroyfind(&find); } fctx->find = NULL; } static void fctx_cleanupaltfinds(fetchctx_t *fctx) { dns_adbfind_t *find, *next_find; REQUIRE(ISC_LIST_EMPTY(fctx->queries)); for (find = ISC_LIST_HEAD(fctx->altfinds); find != NULL; find = next_find) { next_find = ISC_LIST_NEXT(find, publink); ISC_LIST_UNLINK(fctx->altfinds, find, publink); dns_adb_destroyfind(&find); } fctx->altfind = NULL; } static void fctx_cleanupforwaddrs(fetchctx_t *fctx) { dns_adbaddrinfo_t *addr, *next_addr; REQUIRE(ISC_LIST_EMPTY(fctx->queries)); for (addr = ISC_LIST_HEAD(fctx->forwaddrs); addr != NULL; addr = next_addr) { next_addr = ISC_LIST_NEXT(addr, publink); ISC_LIST_UNLINK(fctx->forwaddrs, addr, publink); dns_adb_freeaddrinfo(fctx->adb, &addr); } } static void fctx_cleanupaltaddrs(fetchctx_t *fctx) { dns_adbaddrinfo_t *addr, *next_addr; REQUIRE(ISC_LIST_EMPTY(fctx->queries)); for (addr = ISC_LIST_HEAD(fctx->altaddrs); addr != NULL; addr = next_addr) { next_addr = ISC_LIST_NEXT(addr, publink); ISC_LIST_UNLINK(fctx->altaddrs, addr, publink); dns_adb_freeaddrinfo(fctx->adb, &addr); } } static inline void fctx_stopeverything(fetchctx_t *fctx, isc_boolean_t no_response) { FCTXTRACE("stopeverything"); fctx_cancelqueries(fctx, no_response); fctx_cleanupfinds(fctx); fctx_cleanupaltfinds(fctx); fctx_cleanupforwaddrs(fctx); fctx_cleanupaltaddrs(fctx); fctx_stoptimer(fctx); } static inline void fctx_sendevents(fetchctx_t *fctx, isc_result_t result, int line) { dns_fetchevent_t *event, *next_event; isc_task_t *task; unsigned int count = 0; isc_interval_t i; isc_boolean_t logit = ISC_FALSE; isc_time_t now; unsigned int old_spillat; unsigned int new_spillat = 0; /* initialized to silence compiler warnings */ /* * Caller must be holding the appropriate bucket lock. */ REQUIRE(fctx->state == fetchstate_done); FCTXTRACE("sendevents"); /* * Keep some record of fetch result for logging later (if required). */ fctx->result = result; fctx->exitline = line; TIME_NOW(&now); fctx->duration = isc_time_microdiff(&now, &fctx->start); for (event = ISC_LIST_HEAD(fctx->events); event != NULL; event = next_event) { next_event = ISC_LIST_NEXT(event, ev_link); ISC_LIST_UNLINK(fctx->events, event, ev_link); task = event->ev_sender; event->ev_sender = fctx; event->vresult = fctx->vresult; if (!HAVE_ANSWER(fctx)) event->result = result; INSIST(result != ISC_R_SUCCESS || dns_rdataset_isassociated(event->rdataset) || fctx->type == dns_rdatatype_any || fctx->type == dns_rdatatype_rrsig || fctx->type == dns_rdatatype_sig); /* * Negative results must be indicated in event->result. */ if (dns_rdataset_isassociated(event->rdataset) && NEGATIVE(event->rdataset)) { INSIST(event->result == DNS_R_NCACHENXDOMAIN || event->result == DNS_R_NCACHENXRRSET); } isc_task_sendanddetach(&task, ISC_EVENT_PTR(&event)); count++; } if ((fctx->attributes & FCTX_ATTR_HAVEANSWER) != 0 && fctx->spilled && (count < fctx->res->spillatmax || fctx->res->spillatmax == 0)) { LOCK(&fctx->res->lock); if (count == fctx->res->spillat && !fctx->res->exiting) { old_spillat = fctx->res->spillat; fctx->res->spillat += 5; if (fctx->res->spillat > fctx->res->spillatmax && fctx->res->spillatmax != 0) fctx->res->spillat = fctx->res->spillatmax; new_spillat = fctx->res->spillat; if (new_spillat != old_spillat) { logit = ISC_TRUE; } isc_interval_set(&i, 20 * 60, 0); result = isc_timer_reset(fctx->res->spillattimer, isc_timertype_ticker, NULL, &i, ISC_TRUE); RUNTIME_CHECK(result == ISC_R_SUCCESS); } UNLOCK(&fctx->res->lock); if (logit) isc_log_write(dns_lctx, DNS_LOGCATEGORY_RESOLVER, DNS_LOGMODULE_RESOLVER, ISC_LOG_NOTICE, "clients-per-query increased to %u", new_spillat); } } static inline void log_edns(fetchctx_t *fctx) { char domainbuf[DNS_NAME_FORMATSIZE]; if (fctx->reason == NULL) return; /* * We do not know if fctx->domain is the actual domain the record * lives in or a parent domain so we have a '?' after it. */ dns_name_format(&fctx->domain, domainbuf, sizeof(domainbuf)); isc_log_write(dns_lctx, DNS_LOGCATEGORY_EDNS_DISABLED, DNS_LOGMODULE_RESOLVER, ISC_LOG_INFO, "success resolving '%s' (in '%s'?) after %s", fctx->info, domainbuf, fctx->reason); fctx->reason = NULL; } static void fctx_done(fetchctx_t *fctx, isc_result_t result, int line) { dns_resolver_t *res; isc_boolean_t no_response; REQUIRE(line >= 0); FCTXTRACE("done"); res = fctx->res; if (result == ISC_R_SUCCESS) { /*% * Log any deferred EDNS timeout messages. */ log_edns(fctx); no_response = ISC_TRUE; } else no_response = ISC_FALSE; fctx->reason = NULL; fctx_stopeverything(fctx, no_response); LOCK(&res->buckets[fctx->bucketnum].lock); fctx->state = fetchstate_done; fctx->attributes &= ~FCTX_ATTR_ADDRWAIT; fctx_sendevents(fctx, result, line); UNLOCK(&res->buckets[fctx->bucketnum].lock); } static void process_sendevent(resquery_t *query, isc_event_t *event) { isc_socketevent_t *sevent = (isc_socketevent_t *)event; isc_boolean_t retry = ISC_FALSE; isc_result_t result; fetchctx_t *fctx; fctx = query->fctx; if (RESQUERY_CANCELED(query)) { if (query->sends == 0 && query->connects == 0) { /* * This query was canceled while the * isc_socket_sendto/connect() was in progress. */ if (query->tcpsocket != NULL) isc_socket_detach(&query->tcpsocket); resquery_destroy(&query); } } else { switch (sevent->result) { case ISC_R_SUCCESS: break; case ISC_R_HOSTUNREACH: case ISC_R_NETUNREACH: case ISC_R_NOPERM: case ISC_R_ADDRNOTAVAIL: case ISC_R_CONNREFUSED: /* * No route to remote. */ add_bad(fctx, query->addrinfo, sevent->result, badns_unreachable); fctx_cancelquery(&query, NULL, NULL, ISC_TRUE); retry = ISC_TRUE; break; default: fctx_cancelquery(&query, NULL, NULL, ISC_FALSE); break; } } if (event->ev_type == ISC_SOCKEVENT_CONNECT) isc_event_free(&event); if (retry) { /* * Behave as if the idle timer has expired. For TCP * this may not actually reflect the latest timer. */ fctx->attributes &= ~FCTX_ATTR_ADDRWAIT; result = fctx_stopidletimer(fctx); if (result != ISC_R_SUCCESS) fctx_done(fctx, result, __LINE__); else fctx_try(fctx, ISC_TRUE, ISC_FALSE); } } static void resquery_udpconnected(isc_task_t *task, isc_event_t *event) { resquery_t *query = event->ev_arg; REQUIRE(event->ev_type == ISC_SOCKEVENT_CONNECT); QTRACE("udpconnected"); UNUSED(task); INSIST(RESQUERY_CONNECTING(query)); query->connects--; process_sendevent(query, event); } static void resquery_senddone(isc_task_t *task, isc_event_t *event) { resquery_t *query = event->ev_arg; REQUIRE(event->ev_type == ISC_SOCKEVENT_SENDDONE); QTRACE("senddone"); /* * XXXRTH * * Currently we don't wait for the senddone event before retrying * a query. This means that if we get really behind, we may end * up doing extra work! */ UNUSED(task); INSIST(RESQUERY_SENDING(query)); query->sends--; process_sendevent(query, event); } static inline isc_result_t fctx_addopt(dns_message_t *message, unsigned int version, isc_uint16_t udpsize, dns_ednsopt_t *ednsopts, size_t count) { dns_rdataset_t *rdataset = NULL; isc_result_t result; result = dns_message_buildopt(message, &rdataset, version, udpsize, DNS_MESSAGEEXTFLAG_DO, ednsopts, count); if (result != ISC_R_SUCCESS) return (result); return (dns_message_setopt(message, rdataset)); } static inline void fctx_setretryinterval(fetchctx_t *fctx, unsigned int rtt) { unsigned int seconds; unsigned int us; /* * We retry every .8 seconds the first two times through the address * list, and then we do exponential back-off. */ if (fctx->restarts < 3) us = 800000; else us = (800000 << (fctx->restarts - 2)); /* * Add a fudge factor to the expected rtt based on the current * estimate. */ if (rtt < 50000) rtt += 50000; else if (rtt < 100000) rtt += 100000; else rtt += 200000; /* * Always wait for at least the expected rtt. */ if (us < rtt) us = rtt; /* * But don't ever wait for more than 10 seconds. */ if (us > MAX_SINGLE_QUERY_TIMEOUT_US) us = MAX_SINGLE_QUERY_TIMEOUT_US; seconds = us / US_PER_SEC; us -= seconds * US_PER_SEC; isc_interval_set(&fctx->interval, seconds, us * 1000); } static isc_result_t fctx_query(fetchctx_t *fctx, dns_adbaddrinfo_t *addrinfo, unsigned int options) { dns_resolver_t *res; isc_task_t *task; isc_result_t result; resquery_t *query; isc_sockaddr_t addr; isc_boolean_t have_addr = ISC_FALSE; unsigned int srtt; FCTXTRACE("query"); res = fctx->res; task = res->buckets[fctx->bucketnum].task; srtt = addrinfo->srtt; /* * A forwarder needs to make multiple queries. Give it at least * a second to do these in. */ if (ISFORWARDER(addrinfo) && srtt < 1000000) srtt = 1000000; fctx_setretryinterval(fctx, srtt); result = fctx_startidletimer(fctx, &fctx->interval); if (result != ISC_R_SUCCESS) return (result); INSIST(ISC_LIST_EMPTY(fctx->validators)); dns_message_reset(fctx->rmessage, DNS_MESSAGE_INTENTPARSE); query = isc_mem_get(fctx->mctx, sizeof(*query)); if (query == NULL) { result = ISC_R_NOMEMORY; goto stop_idle_timer; } query->mctx = fctx->mctx; query->options = options; query->attributes = 0; query->sends = 0; query->connects = 0; /* * Note that the caller MUST guarantee that 'addrinfo' will remain * valid until this query is canceled. */ query->addrinfo = addrinfo; TIME_NOW(&query->start); /* * If this is a TCP query, then we need to make a socket and * a dispatch for it here. Otherwise we use the resolver's * shared dispatch. */ query->dispatchmgr = res->dispatchmgr; query->dispatch = NULL; query->exclusivesocket = ISC_FALSE; query->tcpsocket = NULL; if (res->view->peers != NULL) { dns_peer_t *peer = NULL; isc_netaddr_t dstip; isc_netaddr_fromsockaddr(&dstip, &addrinfo->sockaddr); result = dns_peerlist_peerbyaddr(res->view->peers, &dstip, &peer); if (result == ISC_R_SUCCESS) { result = dns_peer_getquerysource(peer, &addr); if (result == ISC_R_SUCCESS) have_addr = ISC_TRUE; } } if ((query->options & DNS_FETCHOPT_TCP) != 0) { int pf; pf = isc_sockaddr_pf(&addrinfo->sockaddr); if (!have_addr) { switch (pf) { case PF_INET: result = dns_dispatch_getlocaladdress( res->dispatches4->dispatches[0], &addr); break; case PF_INET6: result = dns_dispatch_getlocaladdress( res->dispatches6->dispatches[0], &addr); break; default: result = ISC_R_NOTIMPLEMENTED; break; } if (result != ISC_R_SUCCESS) goto cleanup_query; } isc_sockaddr_setport(&addr, 0); result = isc_socket_create(res->socketmgr, pf, isc_sockettype_tcp, &query->tcpsocket); if (result != ISC_R_SUCCESS) goto cleanup_query; #ifndef BROKEN_TCP_BIND_BEFORE_CONNECT result = isc_socket_bind(query->tcpsocket, &addr, 0); if (result != ISC_R_SUCCESS) goto cleanup_socket; #endif /* * A dispatch will be created once the connect succeeds. */ } else { if (have_addr) { unsigned int attrs, attrmask; attrs = DNS_DISPATCHATTR_UDP; switch (isc_sockaddr_pf(&addr)) { case AF_INET: attrs |= DNS_DISPATCHATTR_IPV4; break; case AF_INET6: attrs |= DNS_DISPATCHATTR_IPV6; break; default: result = ISC_R_NOTIMPLEMENTED; goto cleanup_query; } attrmask = DNS_DISPATCHATTR_UDP; attrmask |= DNS_DISPATCHATTR_TCP; attrmask |= DNS_DISPATCHATTR_IPV4; attrmask |= DNS_DISPATCHATTR_IPV6; result = dns_dispatch_getudp(res->dispatchmgr, res->socketmgr, res->taskmgr, &addr, 4096, 1000, 32768, 16411, 16433, attrs, attrmask, &query->dispatch); if (result != ISC_R_SUCCESS) goto cleanup_query; } else { switch (isc_sockaddr_pf(&addrinfo->sockaddr)) { case PF_INET: dns_dispatch_attach( dns_resolver_dispatchv4(res), &query->dispatch); query->exclusivesocket = res->exclusivev4; break; case PF_INET6: dns_dispatch_attach( dns_resolver_dispatchv6(res), &query->dispatch); query->exclusivesocket = res->exclusivev6; break; default: result = ISC_R_NOTIMPLEMENTED; goto cleanup_query; } } /* * We should always have a valid dispatcher here. If we * don't support a protocol family, then its dispatcher * will be NULL, but we shouldn't be finding addresses for * protocol types we don't support, so the dispatcher * we found should never be NULL. */ INSIST(query->dispatch != NULL); } query->dispentry = NULL; query->fctx = fctx; query->tsig = NULL; query->tsigkey = NULL; ISC_LINK_INIT(query, link); query->magic = QUERY_MAGIC; if ((query->options & DNS_FETCHOPT_TCP) != 0) { /* * Connect to the remote server. * * XXXRTH Should we attach to the socket? */ result = isc_socket_connect(query->tcpsocket, &addrinfo->sockaddr, task, resquery_connected, query); if (result != ISC_R_SUCCESS) goto cleanup_socket; query->connects++; QTRACE("connecting via TCP"); } else { result = resquery_send(query); if (result != ISC_R_SUCCESS) goto cleanup_dispatch; } fctx->querysent++; ISC_LIST_APPEND(fctx->queries, query, link); query->fctx->nqueries++; if (isc_sockaddr_pf(&addrinfo->sockaddr) == PF_INET) inc_stats(res, dns_resstatscounter_queryv4); else inc_stats(res, dns_resstatscounter_queryv6); if (res->view->resquerystats != NULL) dns_rdatatypestats_increment(res->view->resquerystats, fctx->type); return (ISC_R_SUCCESS); cleanup_socket: isc_socket_detach(&query->tcpsocket); cleanup_dispatch: if (query->dispatch != NULL) dns_dispatch_detach(&query->dispatch); cleanup_query: if (query->connects == 0) { query->magic = 0; isc_mem_put(fctx->mctx, query, sizeof(*query)); } stop_idle_timer: RUNTIME_CHECK(fctx_stopidletimer(fctx) == ISC_R_SUCCESS); return (result); } static isc_boolean_t bad_edns(fetchctx_t *fctx, isc_sockaddr_t *address) { isc_sockaddr_t *sa; for (sa = ISC_LIST_HEAD(fctx->bad_edns); sa != NULL; sa = ISC_LIST_NEXT(sa, link)) { if (isc_sockaddr_equal(sa, address)) return (ISC_TRUE); } return (ISC_FALSE); } static void add_bad_edns(fetchctx_t *fctx, isc_sockaddr_t *address) { isc_sockaddr_t *sa; if (bad_edns(fctx, address)) return; sa = isc_mem_get(fctx->mctx, sizeof(*sa)); if (sa == NULL) return; *sa = *address; ISC_LIST_INITANDAPPEND(fctx->bad_edns, sa, link); } static isc_boolean_t triededns(fetchctx_t *fctx, isc_sockaddr_t *address) { isc_sockaddr_t *sa; for (sa = ISC_LIST_HEAD(fctx->edns); sa != NULL; sa = ISC_LIST_NEXT(sa, link)) { if (isc_sockaddr_equal(sa, address)) return (ISC_TRUE); } return (ISC_FALSE); } static void add_triededns(fetchctx_t *fctx, isc_sockaddr_t *address) { isc_sockaddr_t *sa; if (triededns(fctx, address)) return; sa = isc_mem_get(fctx->mctx, sizeof(*sa)); if (sa == NULL) return; *sa = *address; ISC_LIST_INITANDAPPEND(fctx->edns, sa, link); } static isc_boolean_t triededns512(fetchctx_t *fctx, isc_sockaddr_t *address) { isc_sockaddr_t *sa; for (sa = ISC_LIST_HEAD(fctx->edns512); sa != NULL; sa = ISC_LIST_NEXT(sa, link)) { if (isc_sockaddr_equal(sa, address)) return (ISC_TRUE); } return (ISC_FALSE); } static void add_triededns512(fetchctx_t *fctx, isc_sockaddr_t *address) { isc_sockaddr_t *sa; if (triededns512(fctx, address)) return; sa = isc_mem_get(fctx->mctx, sizeof(*sa)); if (sa == NULL) return; *sa = *address; ISC_LIST_INITANDAPPEND(fctx->edns512, sa, link); } static isc_boolean_t wouldvalidate(fetchctx_t *fctx) { isc_boolean_t secure_domain; isc_result_t result; if (!fctx->res->view->enablevalidation) return (ISC_FALSE); if (fctx->res->view->dlv != NULL) return (ISC_TRUE); result = dns_view_issecuredomain(fctx->res->view, &fctx->name, &secure_domain); if (result != ISC_R_SUCCESS) return (ISC_FALSE); return (secure_domain); } static isc_result_t resquery_send(resquery_t *query) { fetchctx_t *fctx; isc_result_t result; dns_name_t *qname = NULL; dns_rdataset_t *qrdataset = NULL; isc_region_t r; dns_resolver_t *res; isc_task_t *task; isc_socket_t *socket; isc_buffer_t tcpbuffer; isc_sockaddr_t *address; isc_buffer_t *buffer; isc_netaddr_t ipaddr; dns_tsigkey_t *tsigkey = NULL; dns_peer_t *peer = NULL; isc_boolean_t useedns; dns_compress_t cctx; isc_boolean_t cleanup_cctx = ISC_FALSE; isc_boolean_t secure_domain; isc_boolean_t connecting = ISC_FALSE; dns_ednsopt_t ednsopts[DNS_EDNSOPTIONS]; unsigned ednsopt = 0; fctx = query->fctx; QTRACE("send"); res = fctx->res; task = res->buckets[fctx->bucketnum].task; address = NULL; if ((query->options & DNS_FETCHOPT_TCP) != 0) { /* * Reserve space for the TCP message length. */ isc_buffer_init(&tcpbuffer, query->data, sizeof(query->data)); isc_buffer_init(&query->buffer, query->data + 2, sizeof(query->data) - 2); buffer = &tcpbuffer; } else { isc_buffer_init(&query->buffer, query->data, sizeof(query->data)); buffer = &query->buffer; } result = dns_message_gettempname(fctx->qmessage, &qname); if (result != ISC_R_SUCCESS) goto cleanup_temps; result = dns_message_gettemprdataset(fctx->qmessage, &qrdataset); if (result != ISC_R_SUCCESS) goto cleanup_temps; /* * Get a query id from the dispatch. */ result = dns_dispatch_addresponse2(query->dispatch, &query->addrinfo->sockaddr, task, resquery_response, query, &query->id, &query->dispentry, res->socketmgr); if (result != ISC_R_SUCCESS) goto cleanup_temps; fctx->qmessage->opcode = dns_opcode_query; /* * Set up question. */ dns_name_init(qname, NULL); dns_name_clone(&fctx->name, qname); dns_rdataset_init(qrdataset); dns_rdataset_makequestion(qrdataset, res->rdclass, fctx->type); ISC_LIST_APPEND(qname->list, qrdataset, link); dns_message_addname(fctx->qmessage, qname, DNS_SECTION_QUESTION); qname = NULL; qrdataset = NULL; /* * Set RD if the client has requested that we do a recursive query, * or if we're sending to a forwarder. */ if ((query->options & DNS_FETCHOPT_RECURSIVE) != 0 || ISFORWARDER(query->addrinfo)) fctx->qmessage->flags |= DNS_MESSAGEFLAG_RD; /* * Set CD if the client says don't validate or the question is * under a secure entry point. */ if ((query->options & DNS_FETCHOPT_NOVALIDATE) != 0) { fctx->qmessage->flags |= DNS_MESSAGEFLAG_CD; } else if (res->view->enablevalidation) { result = dns_view_issecuredomain(res->view, &fctx->name, &secure_domain); if (result != ISC_R_SUCCESS) secure_domain = ISC_FALSE; if (res->view->dlv != NULL) secure_domain = ISC_TRUE; if (secure_domain) fctx->qmessage->flags |= DNS_MESSAGEFLAG_CD; } /* * We don't have to set opcode because it defaults to query. */ fctx->qmessage->id = query->id; /* * Convert the question to wire format. */ result = dns_compress_init(&cctx, -1, fctx->res->mctx); if (result != ISC_R_SUCCESS) goto cleanup_message; cleanup_cctx = ISC_TRUE; result = dns_message_renderbegin(fctx->qmessage, &cctx, &query->buffer); if (result != ISC_R_SUCCESS) goto cleanup_message; result = dns_message_rendersection(fctx->qmessage, DNS_SECTION_QUESTION, 0); if (result != ISC_R_SUCCESS) goto cleanup_message; peer = NULL; isc_netaddr_fromsockaddr(&ipaddr, &query->addrinfo->sockaddr); (void) dns_peerlist_peerbyaddr(fctx->res->view->peers, &ipaddr, &peer); /* * The ADB does not know about servers with "edns no". Check this, * and then inform the ADB for future use. */ if ((query->addrinfo->flags & DNS_FETCHOPT_NOEDNS0) == 0 && peer != NULL && dns_peer_getsupportedns(peer, &useedns) == ISC_R_SUCCESS && !useedns) { query->options |= DNS_FETCHOPT_NOEDNS0; dns_adb_changeflags(fctx->adb, query->addrinfo, DNS_FETCHOPT_NOEDNS0, DNS_FETCHOPT_NOEDNS0); } /* Sync NOEDNS0 flag in addrinfo->flags and options now. */ if ((query->addrinfo->flags & DNS_FETCHOPT_NOEDNS0) != 0) query->options |= DNS_FETCHOPT_NOEDNS0; /* * Handle timeouts by reducing the UDP response size to 512 bytes * then if that doesn't work disabling EDNS (includes DO) and CD. * * These timeout can be due to: * * broken nameservers that don't respond to EDNS queries. * * broken/misconfigured firewalls and NAT implementations * that don't handle IP fragmentation. * * broken/misconfigured firewalls that don't handle responses * greater than 512 bytes. * * broken/misconfigured firewalls that don't handle EDNS, DO * or CD. * * packet loss / link outage. */ if (fctx->timeout) { if ((triededns512(fctx, &query->addrinfo->sockaddr) || fctx->timeouts >= (MAX_EDNS0_TIMEOUTS * 2)) && (query->options & DNS_FETCHOPT_NOEDNS0) == 0 && (!EDNSOK(query->addrinfo) || !wouldvalidate(fctx))) { query->options |= DNS_FETCHOPT_NOEDNS0; fctx->reason = "disabling EDNS"; } else if ((triededns(fctx, &query->addrinfo->sockaddr) || fctx->timeouts >= MAX_EDNS0_TIMEOUTS) && (query->options & DNS_FETCHOPT_NOEDNS0) == 0) { query->options |= DNS_FETCHOPT_EDNS512; fctx->reason = "reducing the advertised EDNS UDP " "packet size to 512 octets"; } fctx->timeout = ISC_FALSE; } /* * Use EDNS0, unless the caller doesn't want it, or we know that * the remote server doesn't like it. */ if ((query->options & DNS_FETCHOPT_NOEDNS0) == 0) { if ((query->addrinfo->flags & DNS_FETCHOPT_NOEDNS0) == 0) { unsigned int version = 0; /* Default version. */ unsigned int flags; isc_uint16_t udpsize = res->udpsize; isc_boolean_t reqnsid = res->view->requestnsid; flags = query->addrinfo->flags; if ((flags & DNS_FETCHOPT_EDNSVERSIONSET) != 0) { version = flags & DNS_FETCHOPT_EDNSVERSIONMASK; version >>= DNS_FETCHOPT_EDNSVERSIONSHIFT; } if ((query->options & DNS_FETCHOPT_EDNS512) != 0) udpsize = 512; else if (peer != NULL) (void)dns_peer_getudpsize(peer, &udpsize); /* request NSID for current view or peer? */ if (peer != NULL) (void) dns_peer_getrequestnsid(peer, &reqnsid); if (reqnsid) { INSIST(ednsopt < DNS_EDNSOPTIONS); ednsopts[ednsopt].code = DNS_OPT_NSID; ednsopts[ednsopt].length = 0; ednsopts[ednsopt].value = NULL; ednsopt++; } query->ednsversion = version; result = fctx_addopt(fctx->qmessage, version, udpsize, ednsopts, ednsopt); if (reqnsid && result == ISC_R_SUCCESS) { query->options |= DNS_FETCHOPT_WANTNSID; } else if (result != ISC_R_SUCCESS) { /* * We couldn't add the OPT, but we'll press on. * We're not using EDNS0, so set the NOEDNS0 * bit. */ query->options |= DNS_FETCHOPT_NOEDNS0; query->ednsversion = -1; } } else { /* * We know this server doesn't like EDNS0, so we * won't use it. Set the NOEDNS0 bit since we're * not using EDNS0. */ query->options |= DNS_FETCHOPT_NOEDNS0; query->ednsversion = -1; } } else query->ednsversion = -1; /* * If we need EDNS0 to do this query and aren't using it, we lose. */ if (NEEDEDNS0(fctx) && (query->options & DNS_FETCHOPT_NOEDNS0) != 0) { result = DNS_R_SERVFAIL; goto cleanup_message; } if ((query->options & DNS_FETCHOPT_NOEDNS0) == 0) add_triededns(fctx, &query->addrinfo->sockaddr); if ((query->options & DNS_FETCHOPT_EDNS512) != 0) add_triededns512(fctx, &query->addrinfo->sockaddr); /* * Clear CD if EDNS is not in use. */ if ((query->options & DNS_FETCHOPT_NOEDNS0) != 0) fctx->qmessage->flags &= ~DNS_MESSAGEFLAG_CD; /* * Add TSIG record tailored to the current recipient. */ result = dns_view_getpeertsig(fctx->res->view, &ipaddr, &tsigkey); if (result != ISC_R_SUCCESS && result != ISC_R_NOTFOUND) goto cleanup_message; if (tsigkey != NULL) { result = dns_message_settsigkey(fctx->qmessage, tsigkey); dns_tsigkey_detach(&tsigkey); if (result != ISC_R_SUCCESS) goto cleanup_message; } result = dns_message_rendersection(fctx->qmessage, DNS_SECTION_ADDITIONAL, 0); if (result != ISC_R_SUCCESS) goto cleanup_message; result = dns_message_renderend(fctx->qmessage); if (result != ISC_R_SUCCESS) goto cleanup_message; dns_compress_invalidate(&cctx); cleanup_cctx = ISC_FALSE; if (dns_message_gettsigkey(fctx->qmessage) != NULL) { dns_tsigkey_attach(dns_message_gettsigkey(fctx->qmessage), &query->tsigkey); result = dns_message_getquerytsig(fctx->qmessage, fctx->res->mctx, &query->tsig); if (result != ISC_R_SUCCESS) goto cleanup_message; } /* * If using TCP, write the length of the message at the beginning * of the buffer. */ if ((query->options & DNS_FETCHOPT_TCP) != 0) { isc_buffer_usedregion(&query->buffer, &r); isc_buffer_putuint16(&tcpbuffer, (isc_uint16_t)r.length); isc_buffer_add(&tcpbuffer, r.length); } /* * We're now done with the query message. */ dns_message_reset(fctx->qmessage, DNS_MESSAGE_INTENTRENDER); if (query->exclusivesocket) socket = dns_dispatch_getentrysocket(query->dispentry); else socket = dns_dispatch_getsocket(query->dispatch); /* * Send the query! */ if ((query->options & DNS_FETCHOPT_TCP) == 0) { address = &query->addrinfo->sockaddr; if (query->exclusivesocket) { result = isc_socket_connect(socket, address, task, resquery_udpconnected, query); if (result != ISC_R_SUCCESS) goto cleanup_message; connecting = ISC_TRUE; query->connects++; } } isc_buffer_usedregion(buffer, &r); /* * XXXRTH Make sure we don't send to ourselves! We should probably * prune out these addresses when we get them from the ADB. */ ISC_EVENT_INIT(&query->sendevent, sizeof(query->sendevent), 0, NULL, ISC_SOCKEVENT_SENDDONE, resquery_senddone, query, NULL, NULL, NULL); result = isc_socket_sendto2(socket, &r, task, address, NULL, &query->sendevent, 0); if (result != ISC_R_SUCCESS) { if (connecting) { /* * This query is still connecting. * Mark it as canceled so that it will just be * cleaned up when the connected event is received. * Keep fctx around until the event is processed. */ query->fctx->nqueries++; query->attributes |= RESQUERY_ATTR_CANCELED; } goto cleanup_message; } query->sends++; QTRACE("sent"); return (ISC_R_SUCCESS); cleanup_message: if (cleanup_cctx) dns_compress_invalidate(&cctx); dns_message_reset(fctx->qmessage, DNS_MESSAGE_INTENTRENDER); /* * Stop the dispatcher from listening. */ dns_dispatch_removeresponse(&query->dispentry, NULL); cleanup_temps: if (qname != NULL) dns_message_puttempname(fctx->qmessage, &qname); if (qrdataset != NULL) dns_message_puttemprdataset(fctx->qmessage, &qrdataset); return (result); } static void resquery_connected(isc_task_t *task, isc_event_t *event) { isc_socketevent_t *sevent = (isc_socketevent_t *)event; resquery_t *query = event->ev_arg; isc_boolean_t retry = ISC_FALSE; isc_interval_t interval; isc_result_t result; unsigned int attrs; fetchctx_t *fctx; REQUIRE(event->ev_type == ISC_SOCKEVENT_CONNECT); REQUIRE(VALID_QUERY(query)); QTRACE("connected"); UNUSED(task); /* * XXXRTH * * Currently we don't wait for the connect event before retrying * a query. This means that if we get really behind, we may end * up doing extra work! */ query->connects--; fctx = query->fctx; if (RESQUERY_CANCELED(query)) { /* * This query was canceled while the connect() was in * progress. */ isc_socket_detach(&query->tcpsocket); resquery_destroy(&query); } else { switch (sevent->result) { case ISC_R_SUCCESS: /* * Extend the idle timer for TCP. 20 seconds * should be long enough for a TCP connection to be * established, a single DNS request to be sent, * and the response received. */ isc_interval_set(&interval, 20, 0); result = fctx_startidletimer(query->fctx, &interval); if (result != ISC_R_SUCCESS) { fctx_cancelquery(&query, NULL, NULL, ISC_FALSE); fctx_done(fctx, result, __LINE__); break; } /* * We are connected. Create a dispatcher and * send the query. */ attrs = 0; attrs |= DNS_DISPATCHATTR_TCP; attrs |= DNS_DISPATCHATTR_PRIVATE; attrs |= DNS_DISPATCHATTR_CONNECTED; if (isc_sockaddr_pf(&query->addrinfo->sockaddr) == AF_INET) attrs |= DNS_DISPATCHATTR_IPV4; else attrs |= DNS_DISPATCHATTR_IPV6; attrs |= DNS_DISPATCHATTR_MAKEQUERY; result = dns_dispatch_createtcp(query->dispatchmgr, query->tcpsocket, query->fctx->res->taskmgr, 4096, 2, 1, 1, 3, attrs, &query->dispatch); /* * Regardless of whether dns_dispatch_create() * succeeded or not, we don't need our reference * to the socket anymore. */ isc_socket_detach(&query->tcpsocket); if (result == ISC_R_SUCCESS) result = resquery_send(query); if (result != ISC_R_SUCCESS) { fctx_cancelquery(&query, NULL, NULL, ISC_FALSE); fctx_done(fctx, result, __LINE__); } break; case ISC_R_NETUNREACH: case ISC_R_HOSTUNREACH: case ISC_R_CONNREFUSED: case ISC_R_NOPERM: case ISC_R_ADDRNOTAVAIL: case ISC_R_CONNECTIONRESET: /* * No route to remote. */ isc_socket_detach(&query->tcpsocket); fctx_cancelquery(&query, NULL, NULL, ISC_TRUE); retry = ISC_TRUE; break; default: isc_socket_detach(&query->tcpsocket); fctx_cancelquery(&query, NULL, NULL, ISC_FALSE); break; } } isc_event_free(&event); if (retry) { /* * Behave as if the idle timer has expired. For TCP * connections this may not actually reflect the latest timer. */ fctx->attributes &= ~FCTX_ATTR_ADDRWAIT; result = fctx_stopidletimer(fctx); if (result != ISC_R_SUCCESS) fctx_done(fctx, result, __LINE__); else fctx_try(fctx, ISC_TRUE, ISC_FALSE); } } static void fctx_finddone(isc_task_t *task, isc_event_t *event) { fetchctx_t *fctx; dns_adbfind_t *find; dns_resolver_t *res; isc_boolean_t want_try = ISC_FALSE; isc_boolean_t want_done = ISC_FALSE; isc_boolean_t bucket_empty = ISC_FALSE; unsigned int bucketnum; isc_boolean_t destroy = ISC_FALSE; find = event->ev_sender; fctx = event->ev_arg; REQUIRE(VALID_FCTX(fctx)); res = fctx->res; UNUSED(task); FCTXTRACE("finddone"); bucketnum = fctx->bucketnum; LOCK(&res->buckets[bucketnum].lock); INSIST(fctx->pending > 0); fctx->pending--; if (ADDRWAIT(fctx)) { /* * The fetch is waiting for a name to be found. */ INSIST(!SHUTTINGDOWN(fctx)); fctx->attributes &= ~FCTX_ATTR_ADDRWAIT; if (event->ev_type == DNS_EVENT_ADBMOREADDRESSES) { want_try = ISC_TRUE; } else { fctx->findfail++; if (fctx->pending == 0) { /* * We've got nothing else to wait for and don't * know the answer. There's nothing to do but * fail the fctx. */ want_done = ISC_TRUE; } } } else if (SHUTTINGDOWN(fctx) && fctx->pending == 0 && fctx->nqueries == 0 && ISC_LIST_EMPTY(fctx->validators)) { if (fctx->references == 0) { bucket_empty = fctx_unlink(fctx); destroy = ISC_TRUE; } } UNLOCK(&res->buckets[bucketnum].lock); isc_event_free(&event); dns_adb_destroyfind(&find); if (want_try) fctx_try(fctx, ISC_TRUE, ISC_FALSE); else if (want_done) fctx_done(fctx, ISC_R_FAILURE, __LINE__); else if (destroy) { fctx_destroy(fctx); if (bucket_empty) empty_bucket(res); } } static inline isc_boolean_t bad_server(fetchctx_t *fctx, isc_sockaddr_t *address) { isc_sockaddr_t *sa; for (sa = ISC_LIST_HEAD(fctx->bad); sa != NULL; sa = ISC_LIST_NEXT(sa, link)) { if (isc_sockaddr_equal(sa, address)) return (ISC_TRUE); } return (ISC_FALSE); } static inline isc_boolean_t mark_bad(fetchctx_t *fctx) { dns_adbfind_t *curr; dns_adbaddrinfo_t *addrinfo; isc_boolean_t all_bad = ISC_TRUE; /* * Mark all known bad servers, so we don't try to talk to them * again. */ /* * Mark any bad nameservers. */ for (curr = ISC_LIST_HEAD(fctx->finds); curr != NULL; curr = ISC_LIST_NEXT(curr, publink)) { for (addrinfo = ISC_LIST_HEAD(curr->list); addrinfo != NULL; addrinfo = ISC_LIST_NEXT(addrinfo, publink)) { if (bad_server(fctx, &addrinfo->sockaddr)) addrinfo->flags |= FCTX_ADDRINFO_MARK; else all_bad = ISC_FALSE; } } /* * Mark any bad forwarders. */ for (addrinfo = ISC_LIST_HEAD(fctx->forwaddrs); addrinfo != NULL; addrinfo = ISC_LIST_NEXT(addrinfo, publink)) { if (bad_server(fctx, &addrinfo->sockaddr)) addrinfo->flags |= FCTX_ADDRINFO_MARK; else all_bad = ISC_FALSE; } /* * Mark any bad alternates. */ for (curr = ISC_LIST_HEAD(fctx->altfinds); curr != NULL; curr = ISC_LIST_NEXT(curr, publink)) { for (addrinfo = ISC_LIST_HEAD(curr->list); addrinfo != NULL; addrinfo = ISC_LIST_NEXT(addrinfo, publink)) { if (bad_server(fctx, &addrinfo->sockaddr)) addrinfo->flags |= FCTX_ADDRINFO_MARK; else all_bad = ISC_FALSE; } } for (addrinfo = ISC_LIST_HEAD(fctx->altaddrs); addrinfo != NULL; addrinfo = ISC_LIST_NEXT(addrinfo, publink)) { if (bad_server(fctx, &addrinfo->sockaddr)) addrinfo->flags |= FCTX_ADDRINFO_MARK; else all_bad = ISC_FALSE; } return (all_bad); } static void add_bad(fetchctx_t *fctx, dns_adbaddrinfo_t *addrinfo, isc_result_t reason, badnstype_t badtype) { char namebuf[DNS_NAME_FORMATSIZE]; char addrbuf[ISC_SOCKADDR_FORMATSIZE]; char classbuf[64]; char typebuf[64]; char code[64]; isc_buffer_t b; isc_sockaddr_t *sa; const char *spc = ""; isc_sockaddr_t *address = &addrinfo->sockaddr; if (reason == DNS_R_LAME) fctx->lamecount++; else { switch (badtype) { case badns_unreachable: fctx->neterr++; break; case badns_response: fctx->badresp++; break; case badns_validation: break; /* counted as 'valfail' */ } } if (bad_server(fctx, address)) { /* * We already know this server is bad. */ return; } FCTXTRACE("add_bad"); sa = isc_mem_get(fctx->mctx, sizeof(*sa)); if (sa == NULL) return; *sa = *address; ISC_LIST_INITANDAPPEND(fctx->bad, sa, link); if (reason == DNS_R_LAME) /* already logged */ return; if (reason == DNS_R_UNEXPECTEDRCODE && fctx->rmessage->rcode == dns_rcode_servfail && ISFORWARDER(addrinfo)) return; if (reason == DNS_R_UNEXPECTEDRCODE) { isc_buffer_init(&b, code, sizeof(code) - 1); dns_rcode_totext(fctx->rmessage->rcode, &b); code[isc_buffer_usedlength(&b)] = '\0'; spc = " "; } else if (reason == DNS_R_UNEXPECTEDOPCODE) { isc_buffer_init(&b, code, sizeof(code) - 1); dns_opcode_totext((dns_opcode_t)fctx->rmessage->opcode, &b); code[isc_buffer_usedlength(&b)] = '\0'; spc = " "; } else { code[0] = '\0'; } dns_name_format(&fctx->name, namebuf, sizeof(namebuf)); dns_rdatatype_format(fctx->type, typebuf, sizeof(typebuf)); dns_rdataclass_format(fctx->res->rdclass, classbuf, sizeof(classbuf)); isc_sockaddr_format(address, addrbuf, sizeof(addrbuf)); isc_log_write(dns_lctx, DNS_LOGCATEGORY_LAME_SERVERS, DNS_LOGMODULE_RESOLVER, ISC_LOG_INFO, "error (%s%s%s) resolving '%s/%s/%s': %s", dns_result_totext(reason), spc, code, namebuf, typebuf, classbuf, addrbuf); } /* * Sort addrinfo list by RTT. */ static void sort_adbfind(dns_adbfind_t *find) { dns_adbaddrinfo_t *best, *curr; dns_adbaddrinfolist_t sorted; /* Lame N^2 bubble sort. */ ISC_LIST_INIT(sorted); while (!ISC_LIST_EMPTY(find->list)) { best = ISC_LIST_HEAD(find->list); curr = ISC_LIST_NEXT(best, publink); while (curr != NULL) { if (curr->srtt < best->srtt) best = curr; curr = ISC_LIST_NEXT(curr, publink); } ISC_LIST_UNLINK(find->list, best, publink); ISC_LIST_APPEND(sorted, best, publink); } find->list = sorted; } /* * Sort a list of finds by server RTT. */ static void sort_finds(dns_adbfindlist_t *findlist) { dns_adbfind_t *best, *curr; dns_adbfindlist_t sorted; dns_adbaddrinfo_t *addrinfo, *bestaddrinfo; /* Sort each find's addrinfo list by SRTT. */ for (curr = ISC_LIST_HEAD(*findlist); curr != NULL; curr = ISC_LIST_NEXT(curr, publink)) sort_adbfind(curr); /* Lame N^2 bubble sort. */ ISC_LIST_INIT(sorted); while (!ISC_LIST_EMPTY(*findlist)) { best = ISC_LIST_HEAD(*findlist); bestaddrinfo = ISC_LIST_HEAD(best->list); INSIST(bestaddrinfo != NULL); curr = ISC_LIST_NEXT(best, publink); while (curr != NULL) { addrinfo = ISC_LIST_HEAD(curr->list); INSIST(addrinfo != NULL); if (addrinfo->srtt < bestaddrinfo->srtt) { best = curr; bestaddrinfo = addrinfo; } curr = ISC_LIST_NEXT(curr, publink); } ISC_LIST_UNLINK(*findlist, best, publink); ISC_LIST_APPEND(sorted, best, publink); } *findlist = sorted; } static void findname(fetchctx_t *fctx, dns_name_t *name, in_port_t port, unsigned int options, unsigned int flags, isc_stdtime_t now, isc_boolean_t *need_alternate) { dns_adbaddrinfo_t *ai; dns_adbfind_t *find; dns_resolver_t *res; isc_boolean_t unshared; isc_result_t result; res = fctx->res; unshared = ISC_TF((fctx->options & DNS_FETCHOPT_UNSHARED) != 0); /* * If this name is a subdomain of the query domain, tell * the ADB to start looking using zone/hint data. This keeps us * from getting stuck if the nameserver is beneath the zone cut * and we don't know its address (e.g. because the A record has * expired). */ if (dns_name_issubdomain(name, &fctx->domain)) options |= DNS_ADBFIND_STARTATZONE; options |= DNS_ADBFIND_GLUEOK; options |= DNS_ADBFIND_HINTOK; /* * See what we know about this address. */ find = NULL; result = dns_adb_createfind2(fctx->adb, res->buckets[fctx->bucketnum].task, fctx_finddone, fctx, name, &fctx->name, fctx->type, options, now, NULL, res->view->dstport, fctx->depth + 1, fctx->qc, &find); if (result != ISC_R_SUCCESS) { if (result == DNS_R_ALIAS) { char namebuf[DNS_NAME_FORMATSIZE]; /* * XXXRTH Follow the CNAME/DNAME chain? */ dns_adb_destroyfind(&find); fctx->adberr++; dns_name_format(name, namebuf, sizeof(namebuf)); isc_log_write(dns_lctx, DNS_LOGCATEGORY_CNAME, DNS_LOGMODULE_RESOLVER, ISC_LOG_INFO, "skipping nameserver '%s' because it " "is a CNAME, while resolving '%s'", namebuf, fctx->info); } } else if (!ISC_LIST_EMPTY(find->list)) { /* * We have at least some of the addresses for the * name. */ INSIST((find->options & DNS_ADBFIND_WANTEVENT) == 0); if (flags != 0 || port != 0) { for (ai = ISC_LIST_HEAD(find->list); ai != NULL; ai = ISC_LIST_NEXT(ai, publink)) { ai->flags |= flags; if (port != 0) isc_sockaddr_setport(&ai->sockaddr, port); } } if ((flags & FCTX_ADDRINFO_FORWARDER) != 0) ISC_LIST_APPEND(fctx->altfinds, find, publink); else ISC_LIST_APPEND(fctx->finds, find, publink); } else { /* * We don't know any of the addresses for this * name. */ if ((find->options & DNS_ADBFIND_WANTEVENT) != 0) { /* * We're looking for them and will get an * event about it later. */ fctx->pending++; /* * Bootstrap. */ if (need_alternate != NULL && !*need_alternate && unshared && ((res->dispatches4 == NULL && find->result_v6 != DNS_R_NXDOMAIN) || (res->dispatches6 == NULL && find->result_v4 != DNS_R_NXDOMAIN))) *need_alternate = ISC_TRUE; } else { if ((find->options & DNS_ADBFIND_LAMEPRUNED) != 0) fctx->lamecount++; /* cached lame server */ else fctx->adberr++; /* unreachable server, etc. */ /* * If we know there are no addresses for * the family we are using then try to add * an alternative server. */ if (need_alternate != NULL && !*need_alternate && ((res->dispatches4 == NULL && find->result_v6 == DNS_R_NXRRSET) || (res->dispatches6 == NULL && find->result_v4 == DNS_R_NXRRSET))) *need_alternate = ISC_TRUE; dns_adb_destroyfind(&find); } } } static isc_boolean_t isstrictsubdomain(dns_name_t *name1, dns_name_t *name2) { int order; unsigned int nlabels; dns_namereln_t namereln; namereln = dns_name_fullcompare(name1, name2, &order, &nlabels); return (ISC_TF(namereln == dns_namereln_subdomain)); } static isc_result_t fctx_getaddresses(fetchctx_t *fctx, isc_boolean_t badcache) { dns_rdata_t rdata = DNS_RDATA_INIT; isc_result_t result; dns_resolver_t *res; isc_stdtime_t now; unsigned int stdoptions = 0; isc_sockaddr_t *sa; dns_adbaddrinfo_t *ai; isc_boolean_t all_bad; dns_rdata_ns_t ns; isc_boolean_t need_alternate = ISC_FALSE; FCTXTRACE("getaddresses"); /* * Don't pound on remote servers. (Failsafe!) */ fctx->restarts++; if (fctx->restarts > 10) { FCTXTRACE("too many restarts"); return (DNS_R_SERVFAIL); } res = fctx->res; if (fctx->depth > res->maxdepth) { isc_log_write(dns_lctx, DNS_LOGCATEGORY_RESOLVER, DNS_LOGMODULE_RESOLVER, ISC_LOG_DEBUG(3), "too much NS indirection resolving '%s'", fctx->info); return (DNS_R_SERVFAIL); } /* * Forwarders. */ INSIST(ISC_LIST_EMPTY(fctx->forwaddrs)); INSIST(ISC_LIST_EMPTY(fctx->altaddrs)); /* * If this fctx has forwarders, use them; otherwise use any * selective forwarders specified in the view; otherwise use the * resolver's forwarders (if any). */ sa = ISC_LIST_HEAD(fctx->forwarders); if (sa == NULL) { dns_forwarders_t *forwarders = NULL; dns_name_t *name = &fctx->name; dns_name_t suffix; unsigned int labels; dns_fixedname_t fixed; dns_name_t *domain; /* * DS records are found in the parent server. * Strip label to get the correct forwarder (if any). */ if (dns_rdatatype_atparent(fctx->type) && dns_name_countlabels(name) > 1) { dns_name_init(&suffix, NULL); labels = dns_name_countlabels(name); dns_name_getlabelsequence(name, 1, labels - 1, &suffix); name = &suffix; } dns_fixedname_init(&fixed); domain = dns_fixedname_name(&fixed); result = dns_fwdtable_find2(fctx->res->view->fwdtable, name, domain, &forwarders); if (result == ISC_R_SUCCESS) { sa = ISC_LIST_HEAD(forwarders->addrs); fctx->fwdpolicy = forwarders->fwdpolicy; if (fctx->fwdpolicy == dns_fwdpolicy_only && isstrictsubdomain(domain, &fctx->domain)) { dns_name_free(&fctx->domain, fctx->mctx); dns_name_init(&fctx->domain, NULL); result = dns_name_dup(domain, fctx->mctx, &fctx->domain); if (result != ISC_R_SUCCESS) return (result); } } } while (sa != NULL) { if ((isc_sockaddr_pf(sa) == AF_INET && fctx->res->dispatches4 == NULL) || (isc_sockaddr_pf(sa) == AF_INET6 && fctx->res->dispatches6 == NULL)) { sa = ISC_LIST_NEXT(sa, link); continue; } ai = NULL; result = dns_adb_findaddrinfo(fctx->adb, sa, &ai, 0); /* XXXMLG */ if (result == ISC_R_SUCCESS) { dns_adbaddrinfo_t *cur; ai->flags |= FCTX_ADDRINFO_FORWARDER; cur = ISC_LIST_HEAD(fctx->forwaddrs); while (cur != NULL && cur->srtt < ai->srtt) cur = ISC_LIST_NEXT(cur, publink); if (cur != NULL) ISC_LIST_INSERTBEFORE(fctx->forwaddrs, cur, ai, publink); else ISC_LIST_APPEND(fctx->forwaddrs, ai, publink); } sa = ISC_LIST_NEXT(sa, link); } /* * If the forwarding policy is "only", we don't need the addresses * of the nameservers. */ if (fctx->fwdpolicy == dns_fwdpolicy_only) goto out; /* * Normal nameservers. */ stdoptions = DNS_ADBFIND_WANTEVENT | DNS_ADBFIND_EMPTYEVENT; if (fctx->restarts == 1) { /* * To avoid sending out a flood of queries likely to * result in NXRRSET, we suppress fetches for address * families we don't have the first time through, * provided that we have addresses in some family we * can use. * * We don't want to set this option all the time, since * if fctx->restarts > 1, we've clearly been having trouble * with the addresses we had, so getting more could help. */ stdoptions |= DNS_ADBFIND_AVOIDFETCHES; } if (res->dispatches4 != NULL) stdoptions |= DNS_ADBFIND_INET; if (res->dispatches6 != NULL) stdoptions |= DNS_ADBFIND_INET6; if ((stdoptions & DNS_ADBFIND_ADDRESSMASK) == 0) return (DNS_R_SERVFAIL); isc_stdtime_get(&now); INSIST(ISC_LIST_EMPTY(fctx->finds)); INSIST(ISC_LIST_EMPTY(fctx->altfinds)); for (result = dns_rdataset_first(&fctx->nameservers); result == ISC_R_SUCCESS; result = dns_rdataset_next(&fctx->nameservers)) { dns_rdataset_current(&fctx->nameservers, &rdata); /* * Extract the name from the NS record. */ result = dns_rdata_tostruct(&rdata, &ns, NULL); if (result != ISC_R_SUCCESS) continue; findname(fctx, &ns.name, 0, stdoptions, 0, now, &need_alternate); dns_rdata_reset(&rdata); dns_rdata_freestruct(&ns); } if (result != ISC_R_NOMORE) return (result); /* * Do we need to use 6 to 4? */ if (need_alternate) { int family; alternate_t *a; family = (res->dispatches6 != NULL) ? AF_INET6 : AF_INET; for (a = ISC_LIST_HEAD(fctx->res->alternates); a != NULL; a = ISC_LIST_NEXT(a, link)) { if (!a->isaddress) { findname(fctx, &a->_u._n.name, a->_u._n.port, stdoptions, FCTX_ADDRINFO_FORWARDER, now, NULL); continue; } if (isc_sockaddr_pf(&a->_u.addr) != family) continue; ai = NULL; result = dns_adb_findaddrinfo(fctx->adb, &a->_u.addr, &ai, 0); if (result == ISC_R_SUCCESS) { dns_adbaddrinfo_t *cur; ai->flags |= FCTX_ADDRINFO_FORWARDER; cur = ISC_LIST_HEAD(fctx->altaddrs); while (cur != NULL && cur->srtt < ai->srtt) cur = ISC_LIST_NEXT(cur, publink); if (cur != NULL) ISC_LIST_INSERTBEFORE(fctx->altaddrs, cur, ai, publink); else ISC_LIST_APPEND(fctx->altaddrs, ai, publink); } } } out: /* * Mark all known bad servers. */ all_bad = mark_bad(fctx); /* * How are we doing? */ if (all_bad) { /* * We've got no addresses. */ if (fctx->pending > 0) { /* * We're fetching the addresses, but don't have any * yet. Tell the caller to wait for an answer. */ result = DNS_R_WAIT; } else { isc_time_t expire; isc_interval_t i; /* * We've lost completely. We don't know any * addresses, and the ADB has told us it can't get * them. */ FCTXTRACE("no addresses"); isc_interval_set(&i, DNS_BADCACHE_TTL(fctx), 0); result = isc_time_nowplusinterval(&expire, &i); if (badcache && (fctx->type == dns_rdatatype_dnskey || fctx->type == dns_rdatatype_dlv || fctx->type == dns_rdatatype_ds) && result == ISC_R_SUCCESS) dns_resolver_addbadcache(fctx->res, &fctx->name, fctx->type, &expire); result = ISC_R_FAILURE; } } else { /* * We've found some addresses. We might still be looking * for more addresses. */ sort_finds(&fctx->finds); sort_finds(&fctx->altfinds); result = ISC_R_SUCCESS; } return (result); } static inline void possibly_mark(fetchctx_t *fctx, dns_adbaddrinfo_t *addr) { isc_netaddr_t na; char buf[ISC_NETADDR_FORMATSIZE]; isc_sockaddr_t *sa; isc_boolean_t aborted = ISC_FALSE; isc_boolean_t bogus; dns_acl_t *blackhole; isc_netaddr_t ipaddr; dns_peer_t *peer = NULL; dns_resolver_t *res; const char *msg = NULL; sa = &addr->sockaddr; res = fctx->res; isc_netaddr_fromsockaddr(&ipaddr, sa); blackhole = dns_dispatchmgr_getblackhole(res->dispatchmgr); (void) dns_peerlist_peerbyaddr(res->view->peers, &ipaddr, &peer); if (blackhole != NULL) { int match; if (dns_acl_match(&ipaddr, NULL, blackhole, &res->view->aclenv, &match, NULL) == ISC_R_SUCCESS && match > 0) aborted = ISC_TRUE; } if (peer != NULL && dns_peer_getbogus(peer, &bogus) == ISC_R_SUCCESS && bogus) aborted = ISC_TRUE; if (aborted) { addr->flags |= FCTX_ADDRINFO_MARK; msg = "ignoring blackholed / bogus server: "; } else if (isc_sockaddr_ismulticast(sa)) { addr->flags |= FCTX_ADDRINFO_MARK; msg = "ignoring multicast address: "; } else if (isc_sockaddr_isexperimental(sa)) { addr->flags |= FCTX_ADDRINFO_MARK; msg = "ignoring experimental address: "; } else if (sa->type.sa.sa_family != AF_INET6) { return; } else if (IN6_IS_ADDR_V4MAPPED(&sa->type.sin6.sin6_addr)) { addr->flags |= FCTX_ADDRINFO_MARK; msg = "ignoring IPv6 mapped IPV4 address: "; } else if (IN6_IS_ADDR_V4COMPAT(&sa->type.sin6.sin6_addr)) { addr->flags |= FCTX_ADDRINFO_MARK; msg = "ignoring IPv6 compatibility IPV4 address: "; } else return; if (!isc_log_wouldlog(dns_lctx, ISC_LOG_DEBUG(3))) return; isc_netaddr_fromsockaddr(&na, sa); isc_netaddr_format(&na, buf, sizeof(buf)); FCTXTRACE2(msg, buf); } static inline dns_adbaddrinfo_t * fctx_nextaddress(fetchctx_t *fctx) { dns_adbfind_t *find, *start; dns_adbaddrinfo_t *addrinfo; dns_adbaddrinfo_t *faddrinfo; /* * Return the next untried address, if any. */ /* * Find the first unmarked forwarder (if any). */ for (addrinfo = ISC_LIST_HEAD(fctx->forwaddrs); addrinfo != NULL; addrinfo = ISC_LIST_NEXT(addrinfo, publink)) { if (!UNMARKED(addrinfo)) continue; possibly_mark(fctx, addrinfo); if (UNMARKED(addrinfo)) { addrinfo->flags |= FCTX_ADDRINFO_MARK; fctx->find = NULL; return (addrinfo); } } /* * No forwarders. Move to the next find. */ fctx->attributes |= FCTX_ATTR_TRIEDFIND; find = fctx->find; if (find == NULL) find = ISC_LIST_HEAD(fctx->finds); else { find = ISC_LIST_NEXT(find, publink); if (find == NULL) find = ISC_LIST_HEAD(fctx->finds); } /* * Find the first unmarked addrinfo. */ addrinfo = NULL; if (find != NULL) { start = find; do { for (addrinfo = ISC_LIST_HEAD(find->list); addrinfo != NULL; addrinfo = ISC_LIST_NEXT(addrinfo, publink)) { if (!UNMARKED(addrinfo)) continue; possibly_mark(fctx, addrinfo); if (UNMARKED(addrinfo)) { addrinfo->flags |= FCTX_ADDRINFO_MARK; break; } } if (addrinfo != NULL) break; find = ISC_LIST_NEXT(find, publink); if (find == NULL) find = ISC_LIST_HEAD(fctx->finds); } while (find != start); } fctx->find = find; if (addrinfo != NULL) return (addrinfo); /* * No nameservers left. Try alternates. */ fctx->attributes |= FCTX_ATTR_TRIEDALT; find = fctx->altfind; if (find == NULL) find = ISC_LIST_HEAD(fctx->altfinds); else { find = ISC_LIST_NEXT(find, publink); if (find == NULL) find = ISC_LIST_HEAD(fctx->altfinds); } /* * Find the first unmarked addrinfo. */ addrinfo = NULL; if (find != NULL) { start = find; do { for (addrinfo = ISC_LIST_HEAD(find->list); addrinfo != NULL; addrinfo = ISC_LIST_NEXT(addrinfo, publink)) { if (!UNMARKED(addrinfo)) continue; possibly_mark(fctx, addrinfo); if (UNMARKED(addrinfo)) { addrinfo->flags |= FCTX_ADDRINFO_MARK; break; } } if (addrinfo != NULL) break; find = ISC_LIST_NEXT(find, publink); if (find == NULL) find = ISC_LIST_HEAD(fctx->altfinds); } while (find != start); } faddrinfo = addrinfo; /* * See if we have a better alternate server by address. */ for (addrinfo = ISC_LIST_HEAD(fctx->altaddrs); addrinfo != NULL; addrinfo = ISC_LIST_NEXT(addrinfo, publink)) { if (!UNMARKED(addrinfo)) continue; possibly_mark(fctx, addrinfo); if (UNMARKED(addrinfo) && (faddrinfo == NULL || addrinfo->srtt < faddrinfo->srtt)) { if (faddrinfo != NULL) faddrinfo->flags &= ~FCTX_ADDRINFO_MARK; addrinfo->flags |= FCTX_ADDRINFO_MARK; break; } } if (addrinfo == NULL) { addrinfo = faddrinfo; fctx->altfind = find; } return (addrinfo); } static void fctx_try(fetchctx_t *fctx, isc_boolean_t retrying, isc_boolean_t badcache) { isc_result_t result; dns_adbaddrinfo_t *addrinfo; FCTXTRACE("try"); REQUIRE(!ADDRWAIT(fctx)); /* We've already exceeded maximum query count */ if (isc_counter_used(fctx->qc) > fctx->res->maxqueries) { isc_log_write(dns_lctx, DNS_LOGCATEGORY_RESOLVER, DNS_LOGMODULE_RESOLVER, ISC_LOG_DEBUG(3), "exceeded max queries resolving '%s'", fctx->info); fctx_done(fctx, DNS_R_SERVFAIL, __LINE__); return; } addrinfo = fctx_nextaddress(fctx); if (addrinfo == NULL) { /* * We have no more addresses. Start over. */ fctx_cancelqueries(fctx, ISC_TRUE); fctx_cleanupfinds(fctx); fctx_cleanupaltfinds(fctx); fctx_cleanupforwaddrs(fctx); fctx_cleanupaltaddrs(fctx); result = fctx_getaddresses(fctx, badcache); if (result == DNS_R_WAIT) { /* * Sleep waiting for addresses. */ FCTXTRACE("addrwait"); fctx->attributes |= FCTX_ATTR_ADDRWAIT; return; } else if (result != ISC_R_SUCCESS) { /* * Something bad happened. */ fctx_done(fctx, result, __LINE__); return; } addrinfo = fctx_nextaddress(fctx); /* * While we may have addresses from the ADB, they * might be bad ones. In this case, return SERVFAIL. */ if (addrinfo == NULL) { fctx_done(fctx, DNS_R_SERVFAIL, __LINE__); return; } } if (dns_name_countlabels(&fctx->domain) > 2) { result = isc_counter_increment(fctx->qc); if (result != ISC_R_SUCCESS) { isc_log_write(dns_lctx, DNS_LOGCATEGORY_RESOLVER, DNS_LOGMODULE_RESOLVER, ISC_LOG_DEBUG(3), "exceeded max queries resolving '%s'", fctx->info); fctx_done(fctx, DNS_R_SERVFAIL, __LINE__); return; } } result = fctx_query(fctx, addrinfo, fctx->options); if (result != ISC_R_SUCCESS) fctx_done(fctx, result, __LINE__); else if (retrying) inc_stats(fctx->res, dns_resstatscounter_retry); } static isc_boolean_t fctx_unlink(fetchctx_t *fctx) { dns_resolver_t *res; unsigned int bucketnum; /* * Caller must be holding the bucket lock. */ REQUIRE(VALID_FCTX(fctx)); REQUIRE(fctx->state == fetchstate_done || fctx->state == fetchstate_init); REQUIRE(ISC_LIST_EMPTY(fctx->events)); REQUIRE(ISC_LIST_EMPTY(fctx->queries)); REQUIRE(ISC_LIST_EMPTY(fctx->finds)); REQUIRE(ISC_LIST_EMPTY(fctx->altfinds)); REQUIRE(fctx->pending == 0); REQUIRE(fctx->references == 0); REQUIRE(ISC_LIST_EMPTY(fctx->validators)); FCTXTRACE("unlink"); res = fctx->res; bucketnum = fctx->bucketnum; ISC_LIST_UNLINK(res->buckets[bucketnum].fctxs, fctx, link); LOCK(&res->nlock); res->nfctx--; UNLOCK(&res->nlock); if (res->buckets[bucketnum].exiting && ISC_LIST_EMPTY(res->buckets[bucketnum].fctxs)) return (ISC_TRUE); return (ISC_FALSE); } static void fctx_destroy(fetchctx_t *fctx) { isc_sockaddr_t *sa, *next_sa; REQUIRE(VALID_FCTX(fctx)); REQUIRE(fctx->state == fetchstate_done || fctx->state == fetchstate_init); REQUIRE(ISC_LIST_EMPTY(fctx->events)); REQUIRE(ISC_LIST_EMPTY(fctx->queries)); REQUIRE(ISC_LIST_EMPTY(fctx->finds)); REQUIRE(ISC_LIST_EMPTY(fctx->altfinds)); REQUIRE(fctx->pending == 0); REQUIRE(fctx->references == 0); REQUIRE(ISC_LIST_EMPTY(fctx->validators)); REQUIRE(!ISC_LINK_LINKED(fctx, link)); FCTXTRACE("destroy"); /* * Free bad. */ for (sa = ISC_LIST_HEAD(fctx->bad); sa != NULL; sa = next_sa) { next_sa = ISC_LIST_NEXT(sa, link); ISC_LIST_UNLINK(fctx->bad, sa, link); isc_mem_put(fctx->mctx, sa, sizeof(*sa)); } for (sa = ISC_LIST_HEAD(fctx->edns); sa != NULL; sa = next_sa) { next_sa = ISC_LIST_NEXT(sa, link); ISC_LIST_UNLINK(fctx->edns, sa, link); isc_mem_put(fctx->mctx, sa, sizeof(*sa)); } for (sa = ISC_LIST_HEAD(fctx->edns512); sa != NULL; sa = next_sa) { next_sa = ISC_LIST_NEXT(sa, link); ISC_LIST_UNLINK(fctx->edns512, sa, link); isc_mem_put(fctx->mctx, sa, sizeof(*sa)); } for (sa = ISC_LIST_HEAD(fctx->bad_edns); sa != NULL; sa = next_sa) { next_sa = ISC_LIST_NEXT(sa, link); ISC_LIST_UNLINK(fctx->bad_edns, sa, link); isc_mem_put(fctx->mctx, sa, sizeof(*sa)); } isc_counter_detach(&fctx->qc); isc_timer_detach(&fctx->timer); dns_message_destroy(&fctx->rmessage); dns_message_destroy(&fctx->qmessage); if (dns_name_countlabels(&fctx->domain) > 0) dns_name_free(&fctx->domain, fctx->mctx); if (dns_rdataset_isassociated(&fctx->nameservers)) dns_rdataset_disassociate(&fctx->nameservers); dns_name_free(&fctx->name, fctx->mctx); dns_db_detach(&fctx->cache); dns_adb_detach(&fctx->adb); isc_mem_free(fctx->mctx, fctx->info); isc_mem_putanddetach(&fctx->mctx, fctx, sizeof(*fctx)); } /* * Fetch event handlers. */ static void fctx_timeout(isc_task_t *task, isc_event_t *event) { fetchctx_t *fctx = event->ev_arg; isc_timerevent_t *tevent = (isc_timerevent_t *)event; resquery_t *query; REQUIRE(VALID_FCTX(fctx)); UNUSED(task); FCTXTRACE("timeout"); inc_stats(fctx->res, dns_resstatscounter_querytimeout); if (event->ev_type == ISC_TIMEREVENT_LIFE) { fctx->reason = NULL; fctx_done(fctx, ISC_R_TIMEDOUT, __LINE__); } else { isc_result_t result; fctx->timeouts++; fctx->timeout = ISC_TRUE; /* * We could cancel the running queries here, or we could let * them keep going. Since we normally use separate sockets for * different queries, we adopt the former approach to reduce * the number of open sockets: cancel the oldest query if it * expired after the query had started (this is usually the * case but is not always so, depending on the task schedule * timing). */ query = ISC_LIST_HEAD(fctx->queries); if (query != NULL && isc_time_compare(&tevent->due, &query->start) >= 0) { fctx_cancelquery(&query, NULL, NULL, ISC_TRUE); } fctx->attributes &= ~FCTX_ATTR_ADDRWAIT; /* * Our timer has triggered. Reestablish the fctx lifetime * timer. */ result = fctx_starttimer(fctx); if (result != ISC_R_SUCCESS) fctx_done(fctx, result, __LINE__); else /* * Keep trying. */ fctx_try(fctx, ISC_TRUE, ISC_FALSE); } isc_event_free(&event); } static void fctx_shutdown(fetchctx_t *fctx) { isc_event_t *cevent; /* * Start the shutdown process for fctx, if it isn't already underway. */ FCTXTRACE("shutdown"); /* * The caller must be holding the appropriate bucket lock. */ if (fctx->want_shutdown) return; fctx->want_shutdown = ISC_TRUE; /* * Unless we're still initializing (in which case the * control event is still outstanding), we need to post * the control event to tell the fetch we want it to * exit. */ if (fctx->state != fetchstate_init) { cevent = &fctx->control_event; isc_task_send(fctx->res->buckets[fctx->bucketnum].task, &cevent); } } static void fctx_doshutdown(isc_task_t *task, isc_event_t *event) { fetchctx_t *fctx = event->ev_arg; isc_boolean_t bucket_empty = ISC_FALSE; dns_resolver_t *res; unsigned int bucketnum; dns_validator_t *validator; isc_boolean_t destroy = ISC_FALSE; REQUIRE(VALID_FCTX(fctx)); UNUSED(task); res = fctx->res; bucketnum = fctx->bucketnum; FCTXTRACE("doshutdown"); /* * An fctx that is shutting down is no longer in ADDRWAIT mode. */ fctx->attributes &= ~FCTX_ATTR_ADDRWAIT; /* * Cancel all pending validators. Note that this must be done * without the bucket lock held, since that could cause deadlock. */ validator = ISC_LIST_HEAD(fctx->validators); while (validator != NULL) { dns_validator_cancel(validator); validator = ISC_LIST_NEXT(validator, link); } if (fctx->nsfetch != NULL) dns_resolver_cancelfetch(fctx->nsfetch); /* * Shut down anything that is still running on behalf of this * fetch. To avoid deadlock with the ADB, we must do this * before we lock the bucket lock. */ fctx_stopeverything(fctx, ISC_FALSE); LOCK(&res->buckets[bucketnum].lock); fctx->attributes |= FCTX_ATTR_SHUTTINGDOWN; INSIST(fctx->state == fetchstate_active || fctx->state == fetchstate_done); INSIST(fctx->want_shutdown); if (fctx->state != fetchstate_done) { fctx->state = fetchstate_done; fctx_sendevents(fctx, ISC_R_CANCELED, __LINE__); } if (fctx->references == 0 && fctx->pending == 0 && fctx->nqueries == 0 && ISC_LIST_EMPTY(fctx->validators)) { bucket_empty = fctx_unlink(fctx); destroy = ISC_TRUE; } UNLOCK(&res->buckets[bucketnum].lock); if (destroy) { fctx_destroy(fctx); if (bucket_empty) empty_bucket(res); } } static void fctx_start(isc_task_t *task, isc_event_t *event) { fetchctx_t *fctx = event->ev_arg; isc_boolean_t done = ISC_FALSE, bucket_empty = ISC_FALSE; dns_resolver_t *res; unsigned int bucketnum; isc_boolean_t destroy = ISC_FALSE; REQUIRE(VALID_FCTX(fctx)); UNUSED(task); res = fctx->res; bucketnum = fctx->bucketnum; FCTXTRACE("start"); LOCK(&res->buckets[bucketnum].lock); INSIST(fctx->state == fetchstate_init); if (fctx->want_shutdown) { /* * We haven't started this fctx yet, and we've been requested * to shut it down. */ fctx->attributes |= FCTX_ATTR_SHUTTINGDOWN; fctx->state = fetchstate_done; fctx_sendevents(fctx, ISC_R_CANCELED, __LINE__); /* * Since we haven't started, we INSIST that we have no * pending ADB finds and no pending validations. */ INSIST(fctx->pending == 0); INSIST(fctx->nqueries == 0); INSIST(ISC_LIST_EMPTY(fctx->validators)); if (fctx->references == 0) { /* * It's now safe to destroy this fctx. */ bucket_empty = fctx_unlink(fctx); destroy = ISC_TRUE; } done = ISC_TRUE; } else { /* * Normal fctx startup. */ fctx->state = fetchstate_active; /* * Reset the control event for later use in shutting down * the fctx. */ ISC_EVENT_INIT(event, sizeof(*event), 0, NULL, DNS_EVENT_FETCHCONTROL, fctx_doshutdown, fctx, NULL, NULL, NULL); } UNLOCK(&res->buckets[bucketnum].lock); if (!done) { isc_result_t result; INSIST(!destroy); /* * All is well. Start working on the fetch. */ result = fctx_starttimer(fctx); if (result != ISC_R_SUCCESS) fctx_done(fctx, result, __LINE__); else fctx_try(fctx, ISC_FALSE, ISC_FALSE); } else if (destroy) { fctx_destroy(fctx); if (bucket_empty) empty_bucket(res); } } /* * Fetch Creation, Joining, and Cancelation. */ static inline isc_result_t fctx_join(fetchctx_t *fctx, isc_task_t *task, isc_sockaddr_t *client, dns_messageid_t id, isc_taskaction_t action, void *arg, dns_rdataset_t *rdataset, dns_rdataset_t *sigrdataset, dns_fetch_t *fetch) { isc_task_t *clone; dns_fetchevent_t *event; FCTXTRACE("join"); /* * We store the task we're going to send this event to in the * sender field. We'll make the fetch the sender when we actually * send the event. */ clone = NULL; isc_task_attach(task, &clone); event = (dns_fetchevent_t *) isc_event_allocate(fctx->res->mctx, clone, DNS_EVENT_FETCHDONE, action, arg, sizeof(*event)); if (event == NULL) { isc_task_detach(&clone); return (ISC_R_NOMEMORY); } event->result = DNS_R_SERVFAIL; event->qtype = fctx->type; event->db = NULL; event->node = NULL; event->rdataset = rdataset; event->sigrdataset = sigrdataset; event->fetch = fetch; event->client = client; event->id = id; dns_fixedname_init(&event->foundname); /* * Make sure that we can store the sigrdataset in the * first event if it is needed by any of the events. */ if (event->sigrdataset != NULL) ISC_LIST_PREPEND(fctx->events, event, ev_link); else ISC_LIST_APPEND(fctx->events, event, ev_link); fctx->references++; fctx->client = client; fetch->magic = DNS_FETCH_MAGIC; fetch->private = fctx; return (ISC_R_SUCCESS); } static inline void log_ns_ttl(fetchctx_t *fctx, const char *where) { char namebuf[DNS_NAME_FORMATSIZE]; char domainbuf[DNS_NAME_FORMATSIZE]; dns_name_format(&fctx->name, namebuf, sizeof(namebuf)); dns_name_format(&fctx->domain, domainbuf, sizeof(domainbuf)); isc_log_write(dns_lctx, DNS_LOGCATEGORY_RESOLVER, DNS_LOGMODULE_RESOLVER, ISC_LOG_DEBUG(10), "log_ns_ttl: fctx %p: %s: %s (in '%s'?): %u %u", fctx, where, namebuf, domainbuf, fctx->ns_ttl_ok, fctx->ns_ttl); } static isc_result_t fctx_create(dns_resolver_t *res, dns_name_t *name, dns_rdatatype_t type, dns_name_t *domain, dns_rdataset_t *nameservers, unsigned int options, unsigned int bucketnum, unsigned int depth, isc_counter_t *qc, fetchctx_t **fctxp) { fetchctx_t *fctx; isc_result_t result; isc_result_t iresult; isc_interval_t interval; dns_fixedname_t fixed; unsigned int findoptions = 0; char buf[DNS_NAME_FORMATSIZE + DNS_RDATATYPE_FORMATSIZE]; char typebuf[DNS_RDATATYPE_FORMATSIZE]; dns_name_t suffix; isc_mem_t *mctx; /* * Caller must be holding the lock for bucket number 'bucketnum'. */ REQUIRE(fctxp != NULL && *fctxp == NULL); mctx = res->buckets[bucketnum].mctx; fctx = isc_mem_get(mctx, sizeof(*fctx)); if (fctx == NULL) return (ISC_R_NOMEMORY); fctx->qc = NULL; if (qc != NULL) { isc_counter_attach(qc, &fctx->qc); } else { result = isc_counter_create(res->mctx, res->maxqueries, &fctx->qc); if (result != ISC_R_SUCCESS) goto cleanup_fetch; } /* * Make fctx->info point to a copy of a formatted string * "name/type". */ dns_name_format(name, buf, sizeof(buf)); dns_rdatatype_format(type, typebuf, sizeof(typebuf)); strcat(buf, "/"); /* checked */ strcat(buf, typebuf); /* checked */ fctx->info = isc_mem_strdup(mctx, buf); if (fctx->info == NULL) { result = ISC_R_NOMEMORY; goto cleanup_counter; } FCTXTRACE("create"); dns_name_init(&fctx->name, NULL); result = dns_name_dup(name, mctx, &fctx->name); if (result != ISC_R_SUCCESS) goto cleanup_info; dns_name_init(&fctx->domain, NULL); dns_rdataset_init(&fctx->nameservers); fctx->type = type; fctx->options = options; /* * Note! We do not attach to the task. We are relying on the * resolver to ensure that this task doesn't go away while we are * using it. */ fctx->res = res; fctx->references = 0; fctx->bucketnum = bucketnum; fctx->state = fetchstate_init; fctx->want_shutdown = ISC_FALSE; fctx->cloned = ISC_FALSE; fctx->depth = depth; ISC_LIST_INIT(fctx->queries); ISC_LIST_INIT(fctx->finds); ISC_LIST_INIT(fctx->altfinds); ISC_LIST_INIT(fctx->forwaddrs); ISC_LIST_INIT(fctx->altaddrs); ISC_LIST_INIT(fctx->forwarders); fctx->fwdpolicy = dns_fwdpolicy_none; ISC_LIST_INIT(fctx->bad); ISC_LIST_INIT(fctx->edns); ISC_LIST_INIT(fctx->edns512); ISC_LIST_INIT(fctx->bad_edns); ISC_LIST_INIT(fctx->validators); fctx->validator = NULL; fctx->find = NULL; fctx->altfind = NULL; fctx->pending = 0; fctx->restarts = 0; fctx->querysent = 0; fctx->referrals = 0; TIME_NOW(&fctx->start); fctx->timeouts = 0; fctx->lamecount = 0; fctx->adberr = 0; fctx->neterr = 0; fctx->badresp = 0; fctx->findfail = 0; fctx->valfail = 0; fctx->result = ISC_R_FAILURE; fctx->vresult = ISC_R_SUCCESS; fctx->exitline = -1; /* sentinel */ fctx->logged = ISC_FALSE; fctx->attributes = 0; fctx->spilled = ISC_FALSE; fctx->nqueries = 0; fctx->reason = NULL; fctx->rand_buf = 0; fctx->rand_bits = 0; fctx->timeout = ISC_FALSE; fctx->addrinfo = NULL; fctx->client = NULL; fctx->ns_ttl = 0; fctx->ns_ttl_ok = ISC_FALSE; dns_name_init(&fctx->nsname, NULL); fctx->nsfetch = NULL; dns_rdataset_init(&fctx->nsrrset); if (domain == NULL) { dns_forwarders_t *forwarders = NULL; unsigned int labels; dns_name_t *fwdname = name; /* * DS records are found in the parent server. * Strip label to get the correct forwarder (if any). */ if (dns_rdatatype_atparent(fctx->type) && dns_name_countlabels(name) > 1) { dns_name_init(&suffix, NULL); labels = dns_name_countlabels(name); dns_name_getlabelsequence(name, 1, labels - 1, &suffix); fwdname = &suffix; } dns_fixedname_init(&fixed); domain = dns_fixedname_name(&fixed); result = dns_fwdtable_find2(fctx->res->view->fwdtable, fwdname, domain, &forwarders); if (result == ISC_R_SUCCESS) fctx->fwdpolicy = forwarders->fwdpolicy; if (fctx->fwdpolicy != dns_fwdpolicy_only) { /* * The caller didn't supply a query domain and * nameservers, and we're not in forward-only mode, * so find the best nameservers to use. */ if (dns_rdatatype_atparent(fctx->type)) findoptions |= DNS_DBFIND_NOEXACT; result = dns_view_findzonecut(res->view, name, domain, 0, findoptions, ISC_TRUE, &fctx->nameservers, NULL); if (result != ISC_R_SUCCESS) goto cleanup_name; result = dns_name_dup(domain, mctx, &fctx->domain); if (result != ISC_R_SUCCESS) { dns_rdataset_disassociate(&fctx->nameservers); goto cleanup_name; } fctx->ns_ttl = fctx->nameservers.ttl; fctx->ns_ttl_ok = ISC_TRUE; } else { /* * We're in forward-only mode. Set the query domain. */ result = dns_name_dup(domain, mctx, &fctx->domain); if (result != ISC_R_SUCCESS) goto cleanup_name; } } else { result = dns_name_dup(domain, mctx, &fctx->domain); if (result != ISC_R_SUCCESS) goto cleanup_name; dns_rdataset_clone(nameservers, &fctx->nameservers); fctx->ns_ttl = fctx->nameservers.ttl; fctx->ns_ttl_ok = ISC_TRUE; } log_ns_ttl(fctx, "fctx_create"); INSIST(dns_name_issubdomain(&fctx->name, &fctx->domain)); fctx->qmessage = NULL; result = dns_message_create(mctx, DNS_MESSAGE_INTENTRENDER, &fctx->qmessage); if (result != ISC_R_SUCCESS) goto cleanup_domain; fctx->rmessage = NULL; result = dns_message_create(mctx, DNS_MESSAGE_INTENTPARSE, &fctx->rmessage); if (result != ISC_R_SUCCESS) goto cleanup_qmessage; /* * Compute an expiration time for the entire fetch. */ isc_interval_set(&interval, res->query_timeout, 0); iresult = isc_time_nowplusinterval(&fctx->expires, &interval); if (iresult != ISC_R_SUCCESS) { UNEXPECTED_ERROR(__FILE__, __LINE__, "isc_time_nowplusinterval: %s", isc_result_totext(iresult)); result = ISC_R_UNEXPECTED; goto cleanup_rmessage; } /* * Default retry interval initialization. We set the interval now * mostly so it won't be uninitialized. It will be set to the * correct value before a query is issued. */ isc_interval_set(&fctx->interval, 2, 0); /* * Create an inactive timer. It will be made active when the fetch * is actually started. */ fctx->timer = NULL; iresult = isc_timer_create(res->timermgr, isc_timertype_inactive, NULL, NULL, res->buckets[bucketnum].task, fctx_timeout, fctx, &fctx->timer); if (iresult != ISC_R_SUCCESS) { UNEXPECTED_ERROR(__FILE__, __LINE__, "isc_timer_create: %s", isc_result_totext(iresult)); result = ISC_R_UNEXPECTED; goto cleanup_rmessage; } /* * Attach to the view's cache and adb. */ fctx->cache = NULL; dns_db_attach(res->view->cachedb, &fctx->cache); fctx->adb = NULL; dns_adb_attach(res->view->adb, &fctx->adb); fctx->mctx = NULL; isc_mem_attach(mctx, &fctx->mctx); ISC_LIST_INIT(fctx->events); ISC_LINK_INIT(fctx, link); fctx->magic = FCTX_MAGIC; ISC_LIST_APPEND(res->buckets[bucketnum].fctxs, fctx, link); LOCK(&res->nlock); res->nfctx++; UNLOCK(&res->nlock); *fctxp = fctx; return (ISC_R_SUCCESS); cleanup_rmessage: dns_message_destroy(&fctx->rmessage); cleanup_qmessage: dns_message_destroy(&fctx->qmessage); cleanup_domain: if (dns_name_countlabels(&fctx->domain) > 0) dns_name_free(&fctx->domain, mctx); if (dns_rdataset_isassociated(&fctx->nameservers)) dns_rdataset_disassociate(&fctx->nameservers); cleanup_name: dns_name_free(&fctx->name, mctx); cleanup_info: isc_mem_free(mctx, fctx->info); cleanup_counter: isc_counter_detach(&fctx->qc); cleanup_fetch: isc_mem_put(mctx, fctx, sizeof(*fctx)); return (result); } /* * Handle Responses */ static inline isc_boolean_t is_lame(fetchctx_t *fctx) { dns_message_t *message = fctx->rmessage; dns_name_t *name; dns_rdataset_t *rdataset; isc_result_t result; if (message->rcode != dns_rcode_noerror && message->rcode != dns_rcode_nxdomain) return (ISC_FALSE); if (message->counts[DNS_SECTION_ANSWER] != 0) return (ISC_FALSE); if (message->counts[DNS_SECTION_AUTHORITY] == 0) return (ISC_FALSE); result = dns_message_firstname(message, DNS_SECTION_AUTHORITY); while (result == ISC_R_SUCCESS) { name = NULL; dns_message_currentname(message, DNS_SECTION_AUTHORITY, &name); for (rdataset = ISC_LIST_HEAD(name->list); rdataset != NULL; rdataset = ISC_LIST_NEXT(rdataset, link)) { dns_namereln_t namereln; int order; unsigned int labels; if (rdataset->type != dns_rdatatype_ns) continue; namereln = dns_name_fullcompare(name, &fctx->domain, &order, &labels); if (namereln == dns_namereln_equal && (message->flags & DNS_MESSAGEFLAG_AA) != 0) return (ISC_FALSE); if (namereln == dns_namereln_subdomain) return (ISC_FALSE); return (ISC_TRUE); } result = dns_message_nextname(message, DNS_SECTION_AUTHORITY); } return (ISC_FALSE); } static inline void log_lame(fetchctx_t *fctx, dns_adbaddrinfo_t *addrinfo) { char namebuf[DNS_NAME_FORMATSIZE]; char domainbuf[DNS_NAME_FORMATSIZE]; char addrbuf[ISC_SOCKADDR_FORMATSIZE]; dns_name_format(&fctx->name, namebuf, sizeof(namebuf)); dns_name_format(&fctx->domain, domainbuf, sizeof(domainbuf)); isc_sockaddr_format(&addrinfo->sockaddr, addrbuf, sizeof(addrbuf)); isc_log_write(dns_lctx, DNS_LOGCATEGORY_LAME_SERVERS, DNS_LOGMODULE_RESOLVER, ISC_LOG_INFO, "lame server resolving '%s' (in '%s'?): %s", namebuf, domainbuf, addrbuf); } static inline void log_formerr(fetchctx_t *fctx, const char *format, ...) { char nsbuf[ISC_SOCKADDR_FORMATSIZE]; char clbuf[ISC_SOCKADDR_FORMATSIZE]; const char *clmsg = ""; char msgbuf[2048]; va_list args; va_start(args, format); vsnprintf(msgbuf, sizeof(msgbuf), format, args); va_end(args); isc_sockaddr_format(&fctx->addrinfo->sockaddr, nsbuf, sizeof(nsbuf)); if (fctx->client != NULL) { clmsg = " for client "; isc_sockaddr_format(fctx->client, clbuf, sizeof(clbuf)); } else { clbuf[0] = '\0'; } isc_log_write(dns_lctx, DNS_LOGCATEGORY_RESOLVER, DNS_LOGMODULE_RESOLVER, ISC_LOG_NOTICE, "DNS format error from %s resolving %s%s%s: %s", nsbuf, fctx->info, clmsg, clbuf, msgbuf); } static inline isc_result_t same_question(fetchctx_t *fctx) { isc_result_t result; dns_message_t *message = fctx->rmessage; dns_name_t *name; dns_rdataset_t *rdataset; /* * Caller must be holding the fctx lock. */ /* * XXXRTH Currently we support only one question. */ if (message->counts[DNS_SECTION_QUESTION] != 1) { log_formerr(fctx, "too many questions"); return (DNS_R_FORMERR); } result = dns_message_firstname(message, DNS_SECTION_QUESTION); if (result != ISC_R_SUCCESS) return (result); name = NULL; dns_message_currentname(message, DNS_SECTION_QUESTION, &name); rdataset = ISC_LIST_HEAD(name->list); INSIST(rdataset != NULL); INSIST(ISC_LIST_NEXT(rdataset, link) == NULL); if (fctx->type != rdataset->type || fctx->res->rdclass != rdataset->rdclass || !dns_name_equal(&fctx->name, name)) { char namebuf[DNS_NAME_FORMATSIZE]; char class[DNS_RDATACLASS_FORMATSIZE]; char type[DNS_RDATATYPE_FORMATSIZE]; dns_name_format(name, namebuf, sizeof(namebuf)); dns_rdataclass_format(rdataset->rdclass, class, sizeof(class)); dns_rdatatype_format(rdataset->type, type, sizeof(type)); log_formerr(fctx, "question section mismatch: got %s/%s/%s", namebuf, class, type); return (DNS_R_FORMERR); } return (ISC_R_SUCCESS); } static void clone_results(fetchctx_t *fctx) { dns_fetchevent_t *event, *hevent; isc_result_t result; dns_name_t *name, *hname; FCTXTRACE("clone_results"); /* * Set up any other events to have the same data as the first * event. * * Caller must be holding the appropriate lock. */ fctx->cloned = ISC_TRUE; hevent = ISC_LIST_HEAD(fctx->events); if (hevent == NULL) return; hname = dns_fixedname_name(&hevent->foundname); for (event = ISC_LIST_NEXT(hevent, ev_link); event != NULL; event = ISC_LIST_NEXT(event, ev_link)) { name = dns_fixedname_name(&event->foundname); result = dns_name_copy(hname, name, NULL); if (result != ISC_R_SUCCESS) event->result = result; else event->result = hevent->result; dns_db_attach(hevent->db, &event->db); dns_db_attachnode(hevent->db, hevent->node, &event->node); INSIST(hevent->rdataset != NULL); INSIST(event->rdataset != NULL); if (dns_rdataset_isassociated(hevent->rdataset)) dns_rdataset_clone(hevent->rdataset, event->rdataset); INSIST(! (hevent->sigrdataset == NULL && event->sigrdataset != NULL)); if (hevent->sigrdataset != NULL && dns_rdataset_isassociated(hevent->sigrdataset) && event->sigrdataset != NULL) dns_rdataset_clone(hevent->sigrdataset, event->sigrdataset); } } #define CACHE(r) (((r)->attributes & DNS_RDATASETATTR_CACHE) != 0) #define ANSWER(r) (((r)->attributes & DNS_RDATASETATTR_ANSWER) != 0) #define ANSWERSIG(r) (((r)->attributes & DNS_RDATASETATTR_ANSWERSIG) != 0) #define EXTERNAL(r) (((r)->attributes & DNS_RDATASETATTR_EXTERNAL) != 0) #define CHAINING(r) (((r)->attributes & DNS_RDATASETATTR_CHAINING) != 0) #define CHASE(r) (((r)->attributes & DNS_RDATASETATTR_CHASE) != 0) #define CHECKNAMES(r) (((r)->attributes & DNS_RDATASETATTR_CHECKNAMES) != 0) /* * Destroy '*fctx' if it is ready to be destroyed (i.e., if it has * no references and is no longer waiting for any events). * * Requires: * '*fctx' is shutting down. * * Returns: * true if the resolver is exiting and this is the last fctx in the bucket. */ static isc_boolean_t maybe_destroy(fetchctx_t *fctx, isc_boolean_t locked) { unsigned int bucketnum; isc_boolean_t bucket_empty = ISC_FALSE; dns_resolver_t *res = fctx->res; dns_validator_t *validator, *next_validator; isc_boolean_t destroy = ISC_FALSE; REQUIRE(SHUTTINGDOWN(fctx)); bucketnum = fctx->bucketnum; if (!locked) LOCK(&res->buckets[bucketnum].lock); if (fctx->pending != 0 || fctx->nqueries != 0) goto unlock; for (validator = ISC_LIST_HEAD(fctx->validators); validator != NULL; validator = next_validator) { next_validator = ISC_LIST_NEXT(validator, link); dns_validator_cancel(validator); } if (fctx->references == 0 && ISC_LIST_EMPTY(fctx->validators)) { bucket_empty = fctx_unlink(fctx); destroy = ISC_TRUE; } unlock: if (!locked) UNLOCK(&res->buckets[bucketnum].lock); if (destroy) fctx_destroy(fctx); return (bucket_empty); } /* * The validator has finished. */ static void validated(isc_task_t *task, isc_event_t *event) { dns_adbaddrinfo_t *addrinfo; dns_dbnode_t *node = NULL; dns_dbnode_t *nsnode = NULL; dns_fetchevent_t *hevent; dns_name_t *name; dns_rdataset_t *ardataset = NULL; dns_rdataset_t *asigrdataset = NULL; dns_rdataset_t *rdataset; dns_rdataset_t *sigrdataset; dns_resolver_t *res; dns_valarg_t *valarg; dns_validatorevent_t *vevent; fetchctx_t *fctx; isc_boolean_t chaining; isc_boolean_t negative; isc_boolean_t sentresponse; isc_result_t eresult = ISC_R_SUCCESS; isc_result_t result = ISC_R_SUCCESS; isc_stdtime_t now; isc_uint32_t ttl; isc_uint32_t bucketnum; UNUSED(task); /* for now */ REQUIRE(event->ev_type == DNS_EVENT_VALIDATORDONE); valarg = event->ev_arg; fctx = valarg->fctx; res = fctx->res; addrinfo = valarg->addrinfo; REQUIRE(VALID_FCTX(fctx)); REQUIRE(!ISC_LIST_EMPTY(fctx->validators)); vevent = (dns_validatorevent_t *)event; fctx->vresult = vevent->result; FCTXTRACE("received validation completion event"); bucketnum = fctx->bucketnum; LOCK(&res->buckets[bucketnum].lock); ISC_LIST_UNLINK(fctx->validators, vevent->validator, link); fctx->validator = NULL; /* * Destroy the validator early so that we can * destroy the fctx if necessary. */ dns_validator_destroy(&vevent->validator); isc_mem_put(fctx->mctx, valarg, sizeof(*valarg)); negative = ISC_TF(vevent->rdataset == NULL); sentresponse = ISC_TF((fctx->options & DNS_FETCHOPT_NOVALIDATE) != 0); /* * If shutting down, ignore the results. Check to see if we're * done waiting for validator completions and ADB pending events; if * so, destroy the fctx. */ if (SHUTTINGDOWN(fctx) && !sentresponse) { isc_boolean_t bucket_empty; bucket_empty = maybe_destroy(fctx, ISC_TRUE); UNLOCK(&res->buckets[bucketnum].lock); if (bucket_empty) empty_bucket(res); goto cleanup_event; } isc_stdtime_get(&now); /* * If chaining, we need to make sure that the right result code is * returned, and that the rdatasets are bound. */ if (vevent->result == ISC_R_SUCCESS && !negative && vevent->rdataset != NULL && CHAINING(vevent->rdataset)) { if (vevent->rdataset->type == dns_rdatatype_cname) eresult = DNS_R_CNAME; else { INSIST(vevent->rdataset->type == dns_rdatatype_dname); eresult = DNS_R_DNAME; } chaining = ISC_TRUE; } else chaining = ISC_FALSE; /* * Either we're not shutting down, or we are shutting down but want * to cache the result anyway (if this was a validation started by * a query with cd set) */ hevent = ISC_LIST_HEAD(fctx->events); if (hevent != NULL) { if (!negative && !chaining && (fctx->type == dns_rdatatype_any || fctx->type == dns_rdatatype_rrsig || fctx->type == dns_rdatatype_sig)) { /* * Don't bind rdatasets; the caller * will iterate the node. */ } else { ardataset = hevent->rdataset; asigrdataset = hevent->sigrdataset; } } if (vevent->result != ISC_R_SUCCESS) { FCTXTRACE("validation failed"); inc_stats(res, dns_resstatscounter_valfail); fctx->valfail++; fctx->vresult = vevent->result; if (fctx->vresult != DNS_R_BROKENCHAIN) { result = ISC_R_NOTFOUND; if (vevent->rdataset != NULL) result = dns_db_findnode(fctx->cache, vevent->name, ISC_TRUE, &node); if (result == ISC_R_SUCCESS) (void)dns_db_deleterdataset(fctx->cache, node, NULL, vevent->type, 0); if (result == ISC_R_SUCCESS && vevent->sigrdataset != NULL) (void)dns_db_deleterdataset(fctx->cache, node, NULL, dns_rdatatype_rrsig, vevent->type); if (result == ISC_R_SUCCESS) dns_db_detachnode(fctx->cache, &node); } if (fctx->vresult == DNS_R_BROKENCHAIN && !negative) { /* * Cache the data as pending for later validation. */ result = ISC_R_NOTFOUND; if (vevent->rdataset != NULL) result = dns_db_findnode(fctx->cache, vevent->name, ISC_TRUE, &node); if (result == ISC_R_SUCCESS) { (void)dns_db_addrdataset(fctx->cache, node, NULL, now, vevent->rdataset, 0, NULL); } if (result == ISC_R_SUCCESS && vevent->sigrdataset != NULL) (void)dns_db_addrdataset(fctx->cache, node, NULL, now, vevent->sigrdataset, 0, NULL); if (result == ISC_R_SUCCESS) dns_db_detachnode(fctx->cache, &node); } result = fctx->vresult; add_bad(fctx, addrinfo, result, badns_validation); isc_event_free(&event); UNLOCK(&res->buckets[bucketnum].lock); INSIST(fctx->validator == NULL); fctx->validator = ISC_LIST_HEAD(fctx->validators); if (fctx->validator != NULL) dns_validator_send(fctx->validator); else if (sentresponse) fctx_done(fctx, result, __LINE__); /* Locks bucket. */ else if (result == DNS_R_BROKENCHAIN) { isc_result_t tresult; isc_time_t expire; isc_interval_t i; isc_interval_set(&i, DNS_BADCACHE_TTL(fctx), 0); tresult = isc_time_nowplusinterval(&expire, &i); if (negative && (fctx->type == dns_rdatatype_dnskey || fctx->type == dns_rdatatype_dlv || fctx->type == dns_rdatatype_ds) && tresult == ISC_R_SUCCESS) dns_resolver_addbadcache(res, &fctx->name, fctx->type, &expire); fctx_done(fctx, result, __LINE__); /* Locks bucket. */ } else fctx_try(fctx, ISC_TRUE, ISC_TRUE); /* Locks bucket. */ return; } if (negative) { dns_rdatatype_t covers; FCTXTRACE("nonexistence validation OK"); inc_stats(res, dns_resstatscounter_valnegsuccess); /* * Cache DS NXDOMAIN seperately to other types. */ if (fctx->rmessage->rcode == dns_rcode_nxdomain && fctx->type != dns_rdatatype_ds) covers = dns_rdatatype_any; else covers = fctx->type; result = dns_db_findnode(fctx->cache, vevent->name, ISC_TRUE, &node); if (result != ISC_R_SUCCESS) goto noanswer_response; /* * If we are asking for a SOA record set the cache time * to zero to facilitate locating the containing zone of * a arbitrary zone. */ ttl = res->view->maxncachettl; if (fctx->type == dns_rdatatype_soa && covers == dns_rdatatype_any && res->zero_no_soa_ttl) ttl = 0; result = ncache_adderesult(fctx->rmessage, fctx->cache, node, covers, now, ttl, vevent->optout, vevent->secure, ardataset, &eresult); if (result != ISC_R_SUCCESS) goto noanswer_response; goto answer_response; } else inc_stats(res, dns_resstatscounter_valsuccess); FCTXTRACE("validation OK"); if (vevent->proofs[DNS_VALIDATOR_NOQNAMEPROOF] != NULL) { result = dns_rdataset_addnoqname(vevent->rdataset, vevent->proofs[DNS_VALIDATOR_NOQNAMEPROOF]); RUNTIME_CHECK(result == ISC_R_SUCCESS); INSIST(vevent->sigrdataset != NULL); vevent->sigrdataset->ttl = vevent->rdataset->ttl; if (vevent->proofs[DNS_VALIDATOR_CLOSESTENCLOSER] != NULL) { result = dns_rdataset_addclosest(vevent->rdataset, vevent->proofs[DNS_VALIDATOR_CLOSESTENCLOSER]); RUNTIME_CHECK(result == ISC_R_SUCCESS); } } else if (vevent->rdataset->trust == dns_trust_answer && vevent->rdataset->type != dns_rdatatype_rrsig) { isc_result_t tresult; dns_name_t *noqname = NULL; tresult = findnoqname(fctx, vevent->name, vevent->rdataset->type, &noqname); if (tresult == ISC_R_SUCCESS && noqname != NULL) { tresult = dns_rdataset_addnoqname(vevent->rdataset, noqname); RUNTIME_CHECK(tresult == ISC_R_SUCCESS); } } /* * The data was already cached as pending data. * Re-cache it as secure and bind the cached * rdatasets to the first event on the fetch * event list. */ result = dns_db_findnode(fctx->cache, vevent->name, ISC_TRUE, &node); if (result != ISC_R_SUCCESS) goto noanswer_response; result = dns_db_addrdataset(fctx->cache, node, NULL, now, vevent->rdataset, 0, ardataset); if (result != ISC_R_SUCCESS && result != DNS_R_UNCHANGED) goto noanswer_response; if (ardataset != NULL && NEGATIVE(ardataset)) { if (NXDOMAIN(ardataset)) eresult = DNS_R_NCACHENXDOMAIN; else eresult = DNS_R_NCACHENXRRSET; } else if (vevent->sigrdataset != NULL) { result = dns_db_addrdataset(fctx->cache, node, NULL, now, vevent->sigrdataset, 0, asigrdataset); if (result != ISC_R_SUCCESS && result != DNS_R_UNCHANGED) goto noanswer_response; } if (sentresponse) { isc_boolean_t bucket_empty = ISC_FALSE; /* * If we only deferred the destroy because we wanted to cache * the data, destroy now. */ dns_db_detachnode(fctx->cache, &node); if (SHUTTINGDOWN(fctx)) bucket_empty = maybe_destroy(fctx, ISC_TRUE); UNLOCK(&res->buckets[bucketnum].lock); if (bucket_empty) empty_bucket(res); goto cleanup_event; } if (!ISC_LIST_EMPTY(fctx->validators)) { INSIST(!negative); INSIST(fctx->type == dns_rdatatype_any || fctx->type == dns_rdatatype_rrsig || fctx->type == dns_rdatatype_sig); /* * Don't send a response yet - we have * more rdatasets that still need to * be validated. */ dns_db_detachnode(fctx->cache, &node); UNLOCK(&res->buckets[bucketnum].lock); dns_validator_send(ISC_LIST_HEAD(fctx->validators)); goto cleanup_event; } answer_response: /* * Cache any NS/NSEC records that happened to be validated. */ result = dns_message_firstname(fctx->rmessage, DNS_SECTION_AUTHORITY); while (result == ISC_R_SUCCESS) { name = NULL; dns_message_currentname(fctx->rmessage, DNS_SECTION_AUTHORITY, &name); for (rdataset = ISC_LIST_HEAD(name->list); rdataset != NULL; rdataset = ISC_LIST_NEXT(rdataset, link)) { if ((rdataset->type != dns_rdatatype_ns && rdataset->type != dns_rdatatype_nsec) || rdataset->trust != dns_trust_secure) continue; for (sigrdataset = ISC_LIST_HEAD(name->list); sigrdataset != NULL; sigrdataset = ISC_LIST_NEXT(sigrdataset, link)) { if (sigrdataset->type != dns_rdatatype_rrsig || sigrdataset->covers != rdataset->type) continue; break; } if (sigrdataset == NULL || sigrdataset->trust != dns_trust_secure) continue; result = dns_db_findnode(fctx->cache, name, ISC_TRUE, &nsnode); if (result != ISC_R_SUCCESS) continue; result = dns_db_addrdataset(fctx->cache, nsnode, NULL, now, rdataset, 0, NULL); if (result == ISC_R_SUCCESS) result = dns_db_addrdataset(fctx->cache, nsnode, NULL, now, sigrdataset, 0, NULL); dns_db_detachnode(fctx->cache, &nsnode); if (result != ISC_R_SUCCESS) continue; } result = dns_message_nextname(fctx->rmessage, DNS_SECTION_AUTHORITY); } result = ISC_R_SUCCESS; /* * Respond with an answer, positive or negative, * as opposed to an error. 'node' must be non-NULL. */ fctx->attributes |= FCTX_ATTR_HAVEANSWER; if (hevent != NULL) { /* * Negative results must be indicated in event->result. */ if (dns_rdataset_isassociated(hevent->rdataset) && NEGATIVE(hevent->rdataset)) { INSIST(eresult == DNS_R_NCACHENXDOMAIN || eresult == DNS_R_NCACHENXRRSET); } hevent->result = eresult; RUNTIME_CHECK(dns_name_copy(vevent->name, dns_fixedname_name(&hevent->foundname), NULL) == ISC_R_SUCCESS); dns_db_attach(fctx->cache, &hevent->db); dns_db_transfernode(fctx->cache, &node, &hevent->node); clone_results(fctx); } noanswer_response: if (node != NULL) dns_db_detachnode(fctx->cache, &node); UNLOCK(&res->buckets[bucketnum].lock); fctx_done(fctx, result, __LINE__); /* Locks bucket. */ cleanup_event: INSIST(node == NULL); isc_event_free(&event); } static void fctx_log(void *arg, int level, const char *fmt, ...) { char msgbuf[2048]; va_list args; fetchctx_t *fctx = arg; va_start(args, fmt); vsnprintf(msgbuf, sizeof(msgbuf), fmt, args); va_end(args); isc_log_write(dns_lctx, DNS_LOGCATEGORY_RESOLVER, DNS_LOGMODULE_RESOLVER, level, "fctx %p(%s): %s", fctx, fctx->info, msgbuf); } static inline isc_result_t findnoqname(fetchctx_t *fctx, dns_name_t *name, dns_rdatatype_t type, dns_name_t **noqnamep) { dns_rdataset_t *nrdataset, *next, *sigrdataset; dns_rdata_rrsig_t rrsig; isc_result_t result; unsigned int labels; dns_section_t section; dns_name_t *zonename; dns_fixedname_t fzonename; dns_name_t *closest; dns_fixedname_t fclosest; dns_name_t *nearest; dns_fixedname_t fnearest; dns_rdatatype_t found = dns_rdatatype_none; dns_name_t *noqname = NULL; FCTXTRACE("findnoqname"); REQUIRE(noqnamep != NULL && *noqnamep == NULL); /* * Find the SIG for this rdataset, if we have it. */ for (sigrdataset = ISC_LIST_HEAD(name->list); sigrdataset != NULL; sigrdataset = ISC_LIST_NEXT(sigrdataset, link)) { if (sigrdataset->type == dns_rdatatype_rrsig && sigrdataset->covers == type) break; } if (sigrdataset == NULL) return (ISC_R_NOTFOUND); labels = dns_name_countlabels(name); for (result = dns_rdataset_first(sigrdataset); result == ISC_R_SUCCESS; result = dns_rdataset_next(sigrdataset)) { dns_rdata_t rdata = DNS_RDATA_INIT; dns_rdataset_current(sigrdataset, &rdata); result = dns_rdata_tostruct(&rdata, &rrsig, NULL); RUNTIME_CHECK(result == ISC_R_SUCCESS); /* Wildcard has rrsig.labels < labels - 1. */ if (rrsig.labels + 1U >= labels) continue; break; } if (result == ISC_R_NOMORE) return (ISC_R_NOTFOUND); if (result != ISC_R_SUCCESS) return (result); dns_fixedname_init(&fzonename); zonename = dns_fixedname_name(&fzonename); dns_fixedname_init(&fclosest); closest = dns_fixedname_name(&fclosest); dns_fixedname_init(&fnearest); nearest = dns_fixedname_name(&fnearest); #define NXND(x) ((x) == ISC_R_SUCCESS) section = DNS_SECTION_AUTHORITY; for (result = dns_message_firstname(fctx->rmessage, section); result == ISC_R_SUCCESS; result = dns_message_nextname(fctx->rmessage, section)) { dns_name_t *nsec = NULL; dns_message_currentname(fctx->rmessage, section, &nsec); for (nrdataset = ISC_LIST_HEAD(nsec->list); nrdataset != NULL; nrdataset = next) { isc_boolean_t data = ISC_FALSE, exists = ISC_FALSE; isc_boolean_t optout = ISC_FALSE, unknown = ISC_FALSE; isc_boolean_t setclosest = ISC_FALSE; isc_boolean_t setnearest = ISC_FALSE; next = ISC_LIST_NEXT(nrdataset, link); if (nrdataset->type != dns_rdatatype_nsec && nrdataset->type != dns_rdatatype_nsec3) continue; if (nrdataset->type == dns_rdatatype_nsec && NXND(dns_nsec_noexistnodata(type, name, nsec, nrdataset, &exists, &data, NULL, fctx_log, fctx))) { if (!exists) { noqname = nsec; found = dns_rdatatype_nsec; } } if (nrdataset->type == dns_rdatatype_nsec3 && NXND(dns_nsec3_noexistnodata(type, name, nsec, nrdataset, zonename, &exists, &data, &optout, &unknown, &setclosest, &setnearest, closest, nearest, fctx_log, fctx))) { if (!exists && setnearest) { noqname = nsec; found = dns_rdatatype_nsec3; } } } } if (result == ISC_R_NOMORE) result = ISC_R_SUCCESS; if (noqname != NULL) { for (sigrdataset = ISC_LIST_HEAD(noqname->list); sigrdataset != NULL; sigrdataset = ISC_LIST_NEXT(sigrdataset, link)) { if (sigrdataset->type == dns_rdatatype_rrsig && sigrdataset->covers == found) break; } if (sigrdataset != NULL) *noqnamep = noqname; } return (result); } static inline isc_result_t cache_name(fetchctx_t *fctx, dns_name_t *name, dns_adbaddrinfo_t *addrinfo, isc_stdtime_t now) { dns_rdataset_t *rdataset, *sigrdataset; dns_rdataset_t *addedrdataset, *ardataset, *asigrdataset; dns_rdataset_t *valrdataset = NULL, *valsigrdataset = NULL; dns_dbnode_t *node, **anodep; dns_db_t **adbp; dns_name_t *aname; dns_resolver_t *res; isc_boolean_t need_validation, secure_domain, have_answer; isc_result_t result, eresult; dns_fetchevent_t *event; unsigned int options; isc_task_t *task; isc_boolean_t fail; unsigned int valoptions = 0; /* * The appropriate bucket lock must be held. */ res = fctx->res; need_validation = ISC_FALSE; POST(need_validation); secure_domain = ISC_FALSE; have_answer = ISC_FALSE; eresult = ISC_R_SUCCESS; task = res->buckets[fctx->bucketnum].task; /* * Is DNSSEC validation required for this name? */ if (res->view->enablevalidation) { result = dns_view_issecuredomain(res->view, name, &secure_domain); if (result != ISC_R_SUCCESS) return (result); if (!secure_domain && res->view->dlv != NULL) { valoptions = DNS_VALIDATOR_DLV; secure_domain = ISC_TRUE; } } if ((fctx->options & DNS_FETCHOPT_NOVALIDATE) != 0) need_validation = ISC_FALSE; else need_validation = secure_domain; adbp = NULL; aname = NULL; anodep = NULL; ardataset = NULL; asigrdataset = NULL; event = NULL; if ((name->attributes & DNS_NAMEATTR_ANSWER) != 0 && !need_validation) { have_answer = ISC_TRUE; event = ISC_LIST_HEAD(fctx->events); if (event != NULL) { adbp = &event->db; aname = dns_fixedname_name(&event->foundname); result = dns_name_copy(name, aname, NULL); if (result != ISC_R_SUCCESS) return (result); anodep = &event->node; /* * If this is an ANY, SIG or RRSIG query, we're not * going to return any rdatasets, unless we encountered * a CNAME or DNAME as "the answer". In this case, * we're going to return DNS_R_CNAME or DNS_R_DNAME * and we must set up the rdatasets. */ if ((fctx->type != dns_rdatatype_any && fctx->type != dns_rdatatype_rrsig && fctx->type != dns_rdatatype_sig) || (name->attributes & DNS_NAMEATTR_CHAINING) != 0) { ardataset = event->rdataset; asigrdataset = event->sigrdataset; } } } /* * Find or create the cache node. */ node = NULL; result = dns_db_findnode(fctx->cache, name, ISC_TRUE, &node); if (result != ISC_R_SUCCESS) return (result); /* * Cache or validate each cacheable rdataset. */ fail = ISC_TF((fctx->res->options & DNS_RESOLVER_CHECKNAMESFAIL) != 0); for (rdataset = ISC_LIST_HEAD(name->list); rdataset != NULL; rdataset = ISC_LIST_NEXT(rdataset, link)) { if (!CACHE(rdataset)) continue; if (CHECKNAMES(rdataset)) { char namebuf[DNS_NAME_FORMATSIZE]; char typebuf[DNS_RDATATYPE_FORMATSIZE]; char classbuf[DNS_RDATATYPE_FORMATSIZE]; dns_name_format(name, namebuf, sizeof(namebuf)); dns_rdatatype_format(rdataset->type, typebuf, sizeof(typebuf)); dns_rdataclass_format(rdataset->rdclass, classbuf, sizeof(classbuf)); isc_log_write(dns_lctx, DNS_LOGCATEGORY_RESOLVER, DNS_LOGMODULE_RESOLVER, ISC_LOG_NOTICE, "check-names %s %s/%s/%s", fail ? "failure" : "warning", namebuf, typebuf, classbuf); if (fail) { if (ANSWER(rdataset)) { dns_db_detachnode(fctx->cache, &node); return (DNS_R_BADNAME); } continue; } } /* * Enforce the configure maximum cache TTL. */ if (rdataset->ttl > res->view->maxcachettl) rdataset->ttl = res->view->maxcachettl; /* * Find the SIG for this rdataset, if we have it. */ for (sigrdataset = ISC_LIST_HEAD(name->list); sigrdataset != NULL; sigrdataset = ISC_LIST_NEXT(sigrdataset, link)) { if (sigrdataset->type == dns_rdatatype_rrsig && sigrdataset->covers == rdataset->type) break; } /* * If this RRset is in a secure domain, is in bailiwick, * and is not glue, attempt DNSSEC validation. (We do not * attempt to validate glue or out-of-bailiwick data--even * though there might be some performance benefit to doing * so--because it makes it simpler and safer to ensure that * records from a secure domain are only cached if validated * within the context of a query to the domain that owns * them.) */ if (secure_domain && rdataset->trust != dns_trust_glue && !EXTERNAL(rdataset)) { dns_trust_t trust; /* * RRSIGs are validated as part of validating the * type they cover. */ if (rdataset->type == dns_rdatatype_rrsig) continue; if (sigrdataset == NULL) { if (!ANSWER(rdataset) && need_validation) { /* * Ignore non-answer rdatasets that * are missing signatures. */ continue; } } /* * Normalize the rdataset and sigrdataset TTLs. */ if (sigrdataset != NULL) { rdataset->ttl = ISC_MIN(rdataset->ttl, sigrdataset->ttl); sigrdataset->ttl = rdataset->ttl; } /* * Cache this rdataset/sigrdataset pair as * pending data. Track whether it was additional * or not. */ if (rdataset->trust == dns_trust_additional) trust = dns_trust_pending_additional; else trust = dns_trust_pending_answer; rdataset->trust = trust; if (sigrdataset != NULL) sigrdataset->trust = trust; if (!need_validation || !ANSWER(rdataset)) { if (ANSWER(rdataset) && rdataset->type != dns_rdatatype_rrsig) { isc_result_t tresult; dns_name_t *noqname = NULL; tresult = findnoqname(fctx, name, rdataset->type, &noqname); if (tresult == ISC_R_SUCCESS && noqname != NULL) { tresult = dns_rdataset_addnoqname( rdataset, noqname); RUNTIME_CHECK(tresult == ISC_R_SUCCESS); } } addedrdataset = ardataset; result = dns_db_addrdataset(fctx->cache, node, NULL, now, rdataset, 0, addedrdataset); if (result == DNS_R_UNCHANGED) { result = ISC_R_SUCCESS; if (!need_validation && ardataset != NULL && NEGATIVE(ardataset)) { /* * The answer in the cache is * better than the answer we * found, and is a negative * cache entry, so we must set * eresult appropriately. */ if (NXDOMAIN(ardataset)) eresult = DNS_R_NCACHENXDOMAIN; else eresult = DNS_R_NCACHENXRRSET; /* * We have a negative response * from the cache so don't * attempt to add the RRSIG * rrset. */ continue; } } if (result != ISC_R_SUCCESS) break; if (sigrdataset != NULL) { addedrdataset = asigrdataset; result = dns_db_addrdataset(fctx->cache, node, NULL, now, sigrdataset, 0, addedrdataset); if (result == DNS_R_UNCHANGED) result = ISC_R_SUCCESS; if (result != ISC_R_SUCCESS) break; } else if (!ANSWER(rdataset)) continue; } if (ANSWER(rdataset) && need_validation) { if (fctx->type != dns_rdatatype_any && fctx->type != dns_rdatatype_rrsig && fctx->type != dns_rdatatype_sig) { /* * This is The Answer. We will * validate it, but first we cache * the rest of the response - it may * contain useful keys. */ INSIST(valrdataset == NULL && valsigrdataset == NULL); valrdataset = rdataset; valsigrdataset = sigrdataset; } else { /* * This is one of (potentially) * multiple answers to an ANY * or SIG query. To keep things * simple, we just start the * validator right away rather * than caching first and * having to remember which * rdatasets needed validation. */ result = valcreate(fctx, addrinfo, name, rdataset->type, rdataset, sigrdataset, valoptions, task); /* * Defer any further validations. * This prevents multiple validators * from manipulating fctx->rmessage * simultaneously. */ valoptions |= DNS_VALIDATOR_DEFER; } } else if (CHAINING(rdataset)) { if (rdataset->type == dns_rdatatype_cname) eresult = DNS_R_CNAME; else { INSIST(rdataset->type == dns_rdatatype_dname); eresult = DNS_R_DNAME; } } } else if (!EXTERNAL(rdataset)) { /* * It's OK to cache this rdataset now. */ if (ANSWER(rdataset)) addedrdataset = ardataset; else if (ANSWERSIG(rdataset)) addedrdataset = asigrdataset; else addedrdataset = NULL; if (CHAINING(rdataset)) { if (rdataset->type == dns_rdatatype_cname) eresult = DNS_R_CNAME; else { INSIST(rdataset->type == dns_rdatatype_dname); eresult = DNS_R_DNAME; } } if (rdataset->trust == dns_trust_glue && (rdataset->type == dns_rdatatype_ns || (rdataset->type == dns_rdatatype_rrsig && rdataset->covers == dns_rdatatype_ns))) { /* * If the trust level is 'dns_trust_glue' * then we are adding data from a referral * we got while executing the search algorithm. * New referral data always takes precedence * over the existing cache contents. */ options = DNS_DBADD_FORCE; } else options = 0; if (ANSWER(rdataset) && rdataset->type != dns_rdatatype_rrsig) { isc_result_t tresult; dns_name_t *noqname = NULL; tresult = findnoqname(fctx, name, rdataset->type, &noqname); if (tresult == ISC_R_SUCCESS && noqname != NULL) { tresult = dns_rdataset_addnoqname( rdataset, noqname); RUNTIME_CHECK(tresult == ISC_R_SUCCESS); } } /* * Now we can add the rdataset. */ result = dns_db_addrdataset(fctx->cache, node, NULL, now, rdataset, options, addedrdataset); if (result == DNS_R_UNCHANGED) { if (ANSWER(rdataset) && ardataset != NULL && NEGATIVE(ardataset)) { /* * The answer in the cache is better * than the answer we found, and is * a negative cache entry, so we * must set eresult appropriately. */ if (NXDOMAIN(ardataset)) eresult = DNS_R_NCACHENXDOMAIN; else eresult = DNS_R_NCACHENXRRSET; } result = ISC_R_SUCCESS; } else if (result != ISC_R_SUCCESS) break; } } if (valrdataset != NULL) { dns_rdatatype_t vtype = fctx->type; if (CHAINING(valrdataset)) { if (valrdataset->type == dns_rdatatype_cname) vtype = dns_rdatatype_cname; else vtype = dns_rdatatype_dname; } result = valcreate(fctx, addrinfo, name, vtype, valrdataset, valsigrdataset, valoptions, task); } if (result == ISC_R_SUCCESS && have_answer) { fctx->attributes |= FCTX_ATTR_HAVEANSWER; if (event != NULL) { /* * Negative results must be indicated in event->result. */ if (dns_rdataset_isassociated(event->rdataset) && NEGATIVE(event->rdataset)) { INSIST(eresult == DNS_R_NCACHENXDOMAIN || eresult == DNS_R_NCACHENXRRSET); } event->result = eresult; dns_db_attach(fctx->cache, adbp); dns_db_transfernode(fctx->cache, &node, anodep); clone_results(fctx); } } if (node != NULL) dns_db_detachnode(fctx->cache, &node); return (result); } static inline isc_result_t cache_message(fetchctx_t *fctx, dns_adbaddrinfo_t *addrinfo, isc_stdtime_t now) { isc_result_t result; dns_section_t section; dns_name_t *name; FCTXTRACE("cache_message"); fctx->attributes &= ~FCTX_ATTR_WANTCACHE; LOCK(&fctx->res->buckets[fctx->bucketnum].lock); for (section = DNS_SECTION_ANSWER; section <= DNS_SECTION_ADDITIONAL; section++) { result = dns_message_firstname(fctx->rmessage, section); while (result == ISC_R_SUCCESS) { name = NULL; dns_message_currentname(fctx->rmessage, section, &name); if ((name->attributes & DNS_NAMEATTR_CACHE) != 0) { result = cache_name(fctx, name, addrinfo, now); if (result != ISC_R_SUCCESS) break; } result = dns_message_nextname(fctx->rmessage, section); } if (result != ISC_R_NOMORE) break; } if (result == ISC_R_NOMORE) result = ISC_R_SUCCESS; UNLOCK(&fctx->res->buckets[fctx->bucketnum].lock); return (result); } /* * Do what dns_ncache_addoptout() does, and then compute an appropriate eresult. */ static isc_result_t ncache_adderesult(dns_message_t *message, dns_db_t *cache, dns_dbnode_t *node, dns_rdatatype_t covers, isc_stdtime_t now, dns_ttl_t maxttl, isc_boolean_t optout, isc_boolean_t secure, dns_rdataset_t *ardataset, isc_result_t *eresultp) { isc_result_t result; dns_rdataset_t rdataset; if (ardataset == NULL) { dns_rdataset_init(&rdataset); ardataset = &rdataset; } if (secure) result = dns_ncache_addoptout(message, cache, node, covers, now, maxttl, optout, ardataset); else result = dns_ncache_add(message, cache, node, covers, now, maxttl, ardataset); if (result == DNS_R_UNCHANGED || result == ISC_R_SUCCESS) { /* * If the cache now contains a negative entry and we * care about whether it is DNS_R_NCACHENXDOMAIN or * DNS_R_NCACHENXRRSET then extract it. */ if (NEGATIVE(ardataset)) { /* * The cache data is a negative cache entry. */ if (NXDOMAIN(ardataset)) *eresultp = DNS_R_NCACHENXDOMAIN; else *eresultp = DNS_R_NCACHENXRRSET; } else { /* * Either we don't care about the nature of the * cache rdataset (because no fetch is interested * in the outcome), or the cache rdataset is not * a negative cache entry. Whichever case it is, * we can return success. * * XXXRTH There's a CNAME/DNAME problem here. */ *eresultp = ISC_R_SUCCESS; } result = ISC_R_SUCCESS; } if (ardataset == &rdataset && dns_rdataset_isassociated(ardataset)) dns_rdataset_disassociate(ardataset); return (result); } static inline isc_result_t ncache_message(fetchctx_t *fctx, dns_adbaddrinfo_t *addrinfo, dns_rdatatype_t covers, isc_stdtime_t now) { isc_result_t result, eresult; dns_name_t *name; dns_resolver_t *res; dns_db_t **adbp; dns_dbnode_t *node, **anodep; dns_rdataset_t *ardataset; isc_boolean_t need_validation, secure_domain; dns_name_t *aname; dns_fetchevent_t *event; isc_uint32_t ttl; unsigned int valoptions = 0; FCTXTRACE("ncache_message"); fctx->attributes &= ~FCTX_ATTR_WANTNCACHE; res = fctx->res; need_validation = ISC_FALSE; POST(need_validation); secure_domain = ISC_FALSE; eresult = ISC_R_SUCCESS; name = &fctx->name; node = NULL; /* * XXXMPA remove when we follow cnames and adjust the setting * of FCTX_ATTR_WANTNCACHE in noanswer_response(). */ INSIST(fctx->rmessage->counts[DNS_SECTION_ANSWER] == 0); /* * Is DNSSEC validation required for this name? */ if (fctx->res->view->enablevalidation) { result = dns_view_issecuredomain(res->view, name, &secure_domain); if (result != ISC_R_SUCCESS) return (result); if (!secure_domain && res->view->dlv != NULL) { valoptions = DNS_VALIDATOR_DLV; secure_domain = ISC_TRUE; } } if ((fctx->options & DNS_FETCHOPT_NOVALIDATE) != 0) need_validation = ISC_FALSE; else need_validation = secure_domain; if (secure_domain) { /* * Mark all rdatasets as pending. */ dns_rdataset_t *trdataset; dns_name_t *tname; result = dns_message_firstname(fctx->rmessage, DNS_SECTION_AUTHORITY); while (result == ISC_R_SUCCESS) { tname = NULL; dns_message_currentname(fctx->rmessage, DNS_SECTION_AUTHORITY, &tname); for (trdataset = ISC_LIST_HEAD(tname->list); trdataset != NULL; trdataset = ISC_LIST_NEXT(trdataset, link)) trdataset->trust = dns_trust_pending_answer; result = dns_message_nextname(fctx->rmessage, DNS_SECTION_AUTHORITY); } if (result != ISC_R_NOMORE) return (result); } if (need_validation) { /* * Do negative response validation. */ result = valcreate(fctx, addrinfo, name, fctx->type, NULL, NULL, valoptions, res->buckets[fctx->bucketnum].task); /* * If validation is necessary, return now. Otherwise continue * to process the message, letting the validation complete * in its own good time. */ return (result); } LOCK(&res->buckets[fctx->bucketnum].lock); adbp = NULL; aname = NULL; anodep = NULL; ardataset = NULL; if (!HAVE_ANSWER(fctx)) { event = ISC_LIST_HEAD(fctx->events); if (event != NULL) { adbp = &event->db; aname = dns_fixedname_name(&event->foundname); result = dns_name_copy(name, aname, NULL); if (result != ISC_R_SUCCESS) goto unlock; anodep = &event->node; ardataset = event->rdataset; } } else event = NULL; result = dns_db_findnode(fctx->cache, name, ISC_TRUE, &node); if (result != ISC_R_SUCCESS) goto unlock; /* * If we are asking for a SOA record set the cache time * to zero to facilitate locating the containing zone of * a arbitrary zone. */ ttl = fctx->res->view->maxncachettl; if (fctx->type == dns_rdatatype_soa && covers == dns_rdatatype_any && fctx->res->zero_no_soa_ttl) ttl = 0; result = ncache_adderesult(fctx->rmessage, fctx->cache, node, covers, now, ttl, ISC_FALSE, ISC_FALSE, ardataset, &eresult); if (result != ISC_R_SUCCESS) goto unlock; if (!HAVE_ANSWER(fctx)) { fctx->attributes |= FCTX_ATTR_HAVEANSWER; if (event != NULL) { event->result = eresult; dns_db_attach(fctx->cache, adbp); dns_db_transfernode(fctx->cache, &node, anodep); clone_results(fctx); } } unlock: UNLOCK(&res->buckets[fctx->bucketnum].lock); if (node != NULL) dns_db_detachnode(fctx->cache, &node); return (result); } static inline void mark_related(dns_name_t *name, dns_rdataset_t *rdataset, isc_boolean_t external, isc_boolean_t gluing) { name->attributes |= DNS_NAMEATTR_CACHE; if (gluing) { rdataset->trust = dns_trust_glue; /* * Glue with 0 TTL causes problems. We force the TTL to * 1 second to prevent this. */ if (rdataset->ttl == 0) rdataset->ttl = 1; } else rdataset->trust = dns_trust_additional; /* * Avoid infinite loops by only marking new rdatasets. */ if (!CACHE(rdataset)) { name->attributes |= DNS_NAMEATTR_CHASE; rdataset->attributes |= DNS_RDATASETATTR_CHASE; } rdataset->attributes |= DNS_RDATASETATTR_CACHE; if (external) rdataset->attributes |= DNS_RDATASETATTR_EXTERNAL; } static isc_result_t check_section(void *arg, dns_name_t *addname, dns_rdatatype_t type, dns_section_t section) { fetchctx_t *fctx = arg; isc_result_t result; dns_name_t *name; dns_rdataset_t *rdataset; isc_boolean_t external; dns_rdatatype_t rtype; isc_boolean_t gluing; REQUIRE(VALID_FCTX(fctx)); #if CHECK_FOR_GLUE_IN_ANSWER if (section == DNS_SECTION_ANSWER && type != dns_rdatatype_a) return (ISC_R_SUCCESS); #endif if (GLUING(fctx)) gluing = ISC_TRUE; else gluing = ISC_FALSE; name = NULL; rdataset = NULL; result = dns_message_findname(fctx->rmessage, section, addname, dns_rdatatype_any, 0, &name, NULL); if (result == ISC_R_SUCCESS) { external = ISC_TF(!dns_name_issubdomain(name, &fctx->domain)); if (type == dns_rdatatype_a) { for (rdataset = ISC_LIST_HEAD(name->list); rdataset != NULL; rdataset = ISC_LIST_NEXT(rdataset, link)) { if (rdataset->type == dns_rdatatype_rrsig) rtype = rdataset->covers; else rtype = rdataset->type; if (rtype == dns_rdatatype_a || rtype == dns_rdatatype_aaaa) mark_related(name, rdataset, external, gluing); } } else { result = dns_message_findtype(name, type, 0, &rdataset); if (result == ISC_R_SUCCESS) { mark_related(name, rdataset, external, gluing); /* * Do we have its SIG too? */ rdataset = NULL; result = dns_message_findtype(name, dns_rdatatype_rrsig, type, &rdataset); if (result == ISC_R_SUCCESS) mark_related(name, rdataset, external, gluing); } } } return (ISC_R_SUCCESS); } static isc_result_t check_related(void *arg, dns_name_t *addname, dns_rdatatype_t type) { return (check_section(arg, addname, type, DNS_SECTION_ADDITIONAL)); } #ifndef CHECK_FOR_GLUE_IN_ANSWER #define CHECK_FOR_GLUE_IN_ANSWER 0 #endif #if CHECK_FOR_GLUE_IN_ANSWER static isc_result_t check_answer(void *arg, dns_name_t *addname, dns_rdatatype_t type) { return (check_section(arg, addname, type, DNS_SECTION_ANSWER)); } #endif static void chase_additional(fetchctx_t *fctx) { isc_boolean_t rescan; dns_section_t section = DNS_SECTION_ADDITIONAL; isc_result_t result; again: rescan = ISC_FALSE; for (result = dns_message_firstname(fctx->rmessage, section); result == ISC_R_SUCCESS; result = dns_message_nextname(fctx->rmessage, section)) { dns_name_t *name = NULL; dns_rdataset_t *rdataset; dns_message_currentname(fctx->rmessage, DNS_SECTION_ADDITIONAL, &name); if ((name->attributes & DNS_NAMEATTR_CHASE) == 0) continue; name->attributes &= ~DNS_NAMEATTR_CHASE; for (rdataset = ISC_LIST_HEAD(name->list); rdataset != NULL; rdataset = ISC_LIST_NEXT(rdataset, link)) { if (CHASE(rdataset)) { rdataset->attributes &= ~DNS_RDATASETATTR_CHASE; (void)dns_rdataset_additionaldata(rdataset, check_related, fctx); rescan = ISC_TRUE; } } } if (rescan) goto again; } static inline isc_result_t cname_target(dns_rdataset_t *rdataset, dns_name_t *tname) { isc_result_t result; dns_rdata_t rdata = DNS_RDATA_INIT; dns_rdata_cname_t cname; result = dns_rdataset_first(rdataset); if (result != ISC_R_SUCCESS) return (result); dns_rdataset_current(rdataset, &rdata); result = dns_rdata_tostruct(&rdata, &cname, NULL); if (result != ISC_R_SUCCESS) return (result); dns_name_init(tname, NULL); dns_name_clone(&cname.cname, tname); dns_rdata_freestruct(&cname); return (ISC_R_SUCCESS); } static inline isc_result_t dname_target(fetchctx_t *fctx, dns_rdataset_t *rdataset, dns_name_t *qname, dns_name_t *oname, dns_fixedname_t *fixeddname) { isc_result_t result; dns_rdata_t rdata = DNS_RDATA_INIT; unsigned int nlabels; int order; dns_namereln_t namereln; dns_rdata_dname_t dname; dns_fixedname_t prefix; /* * Get the target name of the DNAME. */ result = dns_rdataset_first(rdataset); if (result != ISC_R_SUCCESS) return (result); dns_rdataset_current(rdataset, &rdata); result = dns_rdata_tostruct(&rdata, &dname, NULL); if (result != ISC_R_SUCCESS) return (result); /* * Get the prefix of qname. */ namereln = dns_name_fullcompare(qname, oname, &order, &nlabels); if (namereln != dns_namereln_subdomain) { char qbuf[DNS_NAME_FORMATSIZE]; char obuf[DNS_NAME_FORMATSIZE]; dns_rdata_freestruct(&dname); dns_name_format(qname, qbuf, sizeof(qbuf)); dns_name_format(oname, obuf, sizeof(obuf)); log_formerr(fctx, "unrelated DNAME in answer: " "%s is not in %s", qbuf, obuf); return (DNS_R_FORMERR); } dns_fixedname_init(&prefix); dns_name_split(qname, nlabels, dns_fixedname_name(&prefix), NULL); dns_fixedname_init(fixeddname); result = dns_name_concatenate(dns_fixedname_name(&prefix), &dname.dname, dns_fixedname_name(fixeddname), NULL); dns_rdata_freestruct(&dname); return (result); } static isc_boolean_t is_answeraddress_allowed(dns_view_t *view, dns_name_t *name, dns_rdataset_t *rdataset) { isc_result_t result; dns_rdata_t rdata = DNS_RDATA_INIT; struct in_addr ina; struct in6_addr in6a; isc_netaddr_t netaddr; char addrbuf[ISC_NETADDR_FORMATSIZE]; char namebuf[DNS_NAME_FORMATSIZE]; char classbuf[64]; char typebuf[64]; int match; /* By default, we allow any addresses. */ if (view->denyansweracl == NULL) return (ISC_TRUE); /* * If the owner name matches one in the exclusion list, either exactly * or partially, allow it. */ if (view->answeracl_exclude != NULL) { dns_rbtnode_t *node = NULL; result = dns_rbt_findnode(view->answeracl_exclude, name, NULL, &node, NULL, 0, NULL, NULL); if (result == ISC_R_SUCCESS || result == DNS_R_PARTIALMATCH) return (ISC_TRUE); } /* * Otherwise, search the filter list for a match for each address * record. If a match is found, the address should be filtered, * so should the entire answer. */ for (result = dns_rdataset_first(rdataset); result == ISC_R_SUCCESS; result = dns_rdataset_next(rdataset)) { dns_rdata_reset(&rdata); dns_rdataset_current(rdataset, &rdata); if (rdataset->type == dns_rdatatype_a) { INSIST(rdata.length == sizeof(ina.s_addr)); memmove(&ina.s_addr, rdata.data, sizeof(ina.s_addr)); isc_netaddr_fromin(&netaddr, &ina); } else { INSIST(rdata.length == sizeof(in6a.s6_addr)); memmove(in6a.s6_addr, rdata.data, sizeof(in6a.s6_addr)); isc_netaddr_fromin6(&netaddr, &in6a); } result = dns_acl_match(&netaddr, NULL, view->denyansweracl, &view->aclenv, &match, NULL); if (result == ISC_R_SUCCESS && match > 0) { isc_netaddr_format(&netaddr, addrbuf, sizeof(addrbuf)); dns_name_format(name, namebuf, sizeof(namebuf)); dns_rdatatype_format(rdataset->type, typebuf, sizeof(typebuf)); dns_rdataclass_format(rdataset->rdclass, classbuf, sizeof(classbuf)); isc_log_write(dns_lctx, DNS_LOGCATEGORY_RESOLVER, DNS_LOGMODULE_RESOLVER, ISC_LOG_NOTICE, "answer address %s denied for %s/%s/%s", addrbuf, namebuf, typebuf, classbuf); return (ISC_FALSE); } } return (ISC_TRUE); } static isc_boolean_t is_answertarget_allowed(dns_view_t *view, dns_name_t *name, dns_rdatatype_t type, dns_name_t *tname, dns_name_t *domain) { isc_result_t result; dns_rbtnode_t *node = NULL; char qnamebuf[DNS_NAME_FORMATSIZE]; char tnamebuf[DNS_NAME_FORMATSIZE]; char classbuf[64]; char typebuf[64]; /* By default, we allow any target name. */ if (view->denyanswernames == NULL) return (ISC_TRUE); /* * If the owner name matches one in the exclusion list, either exactly * or partially, allow it. */ if (view->answernames_exclude != NULL) { result = dns_rbt_findnode(view->answernames_exclude, name, NULL, &node, NULL, 0, NULL, NULL); if (result == ISC_R_SUCCESS || result == DNS_R_PARTIALMATCH) return (ISC_TRUE); } /* * If the target name is a subdomain of the search domain, allow it. */ if (dns_name_issubdomain(tname, domain)) return (ISC_TRUE); /* * Otherwise, apply filters. */ result = dns_rbt_findnode(view->denyanswernames, tname, NULL, &node, NULL, 0, NULL, NULL); if (result == ISC_R_SUCCESS || result == DNS_R_PARTIALMATCH) { dns_name_format(name, qnamebuf, sizeof(qnamebuf)); dns_name_format(tname, tnamebuf, sizeof(tnamebuf)); dns_rdatatype_format(type, typebuf, sizeof(typebuf)); dns_rdataclass_format(view->rdclass, classbuf, sizeof(classbuf)); isc_log_write(dns_lctx, DNS_LOGCATEGORY_RESOLVER, DNS_LOGMODULE_RESOLVER, ISC_LOG_NOTICE, "%s target %s denied for %s/%s", typebuf, tnamebuf, qnamebuf, classbuf); return (ISC_FALSE); } return (ISC_TRUE); } static void trim_ns_ttl(fetchctx_t *fctx, dns_name_t *name, dns_rdataset_t *rdataset) { char ns_namebuf[DNS_NAME_FORMATSIZE]; char namebuf[DNS_NAME_FORMATSIZE]; char tbuf[DNS_RDATATYPE_FORMATSIZE]; if (fctx->ns_ttl_ok && rdataset->ttl > fctx->ns_ttl) { dns_name_format(name, ns_namebuf, sizeof(ns_namebuf)); dns_name_format(&fctx->name, namebuf, sizeof(namebuf)); dns_rdatatype_format(fctx->type, tbuf, sizeof(tbuf)); isc_log_write(dns_lctx, DNS_LOGCATEGORY_RESOLVER, DNS_LOGMODULE_RESOLVER, ISC_LOG_DEBUG(10), "fctx %p: trimming ttl of %s/NS for %s/%s: " "%u -> %u", fctx, ns_namebuf, namebuf, tbuf, rdataset->ttl, fctx->ns_ttl); rdataset->ttl = fctx->ns_ttl; } } /* * Handle a no-answer response (NXDOMAIN, NXRRSET, or referral). * If look_in_options has LOOK_FOR_NS_IN_ANSWER then we look in the answer * section for the NS RRset if the query type is NS; if it has * LOOK_FOR_GLUE_IN_ANSWER we look for glue incorrectly returned in the answer * section for A and AAAA queries. */ #define LOOK_FOR_NS_IN_ANSWER 0x1 #define LOOK_FOR_GLUE_IN_ANSWER 0x2 static isc_result_t noanswer_response(fetchctx_t *fctx, dns_name_t *oqname, unsigned int look_in_options) { isc_result_t result; dns_message_t *message; dns_name_t *name, *qname, *ns_name, *soa_name, *ds_name, *save_name; dns_rdataset_t *rdataset, *ns_rdataset; isc_boolean_t aa, negative_response; dns_rdatatype_t type, save_type; dns_section_t section; FCTXTRACE("noanswer_response"); if ((look_in_options & LOOK_FOR_NS_IN_ANSWER) != 0) { INSIST(fctx->type == dns_rdatatype_ns); section = DNS_SECTION_ANSWER; } else section = DNS_SECTION_AUTHORITY; message = fctx->rmessage; /* * Setup qname. */ if (oqname == NULL) { /* * We have a normal, non-chained negative response or * referral. */ if ((message->flags & DNS_MESSAGEFLAG_AA) != 0) aa = ISC_TRUE; else aa = ISC_FALSE; qname = &fctx->name; } else { /* * We're being invoked by answer_response() after it has * followed a CNAME/DNAME chain. */ qname = oqname; aa = ISC_FALSE; /* * If the current qname is not a subdomain of the query * domain, there's no point in looking at the authority * section without doing DNSSEC validation. * * Until we do that validation, we'll just return success * in this case. */ if (!dns_name_issubdomain(qname, &fctx->domain)) return (ISC_R_SUCCESS); } /* * We have to figure out if this is a negative response, or a * referral. */ /* * Sometimes we can tell if its a negative response by looking at * the message header. */ negative_response = ISC_FALSE; if (message->rcode == dns_rcode_nxdomain || (message->counts[DNS_SECTION_ANSWER] == 0 && message->counts[DNS_SECTION_AUTHORITY] == 0)) negative_response = ISC_TRUE; /* * Process the authority section. */ ns_name = NULL; ns_rdataset = NULL; soa_name = NULL; ds_name = NULL; save_name = NULL; save_type = dns_rdatatype_none; result = dns_message_firstname(message, section); while (result == ISC_R_SUCCESS) { name = NULL; dns_message_currentname(message, section, &name); if (dns_name_issubdomain(name, &fctx->domain)) { /* * Look for NS/SOA RRsets first. */ for (rdataset = ISC_LIST_HEAD(name->list); rdataset != NULL; rdataset = ISC_LIST_NEXT(rdataset, link)) { type = rdataset->type; if (type == dns_rdatatype_rrsig) type = rdataset->covers; if (((type == dns_rdatatype_ns || type == dns_rdatatype_soa) && !dns_name_issubdomain(qname, name))) { char qbuf[DNS_NAME_FORMATSIZE]; char nbuf[DNS_NAME_FORMATSIZE]; char tbuf[DNS_RDATATYPE_FORMATSIZE]; dns_rdatatype_format(type, tbuf, sizeof(tbuf)); dns_name_format(name, nbuf, sizeof(nbuf)); dns_name_format(qname, qbuf, sizeof(qbuf)); log_formerr(fctx, "unrelated %s %s in " "%s authority section", tbuf, nbuf, qbuf); goto nextname; } if (type == dns_rdatatype_ns) { /* * NS or RRSIG NS. * * Only one set of NS RRs is allowed. */ if (rdataset->type == dns_rdatatype_ns) { if (ns_name != NULL && name != ns_name) { log_formerr(fctx, "multiple NS " "RRsets in " "authority " "section"); return (DNS_R_FORMERR); } ns_name = name; ns_rdataset = rdataset; } name->attributes |= DNS_NAMEATTR_CACHE; rdataset->attributes |= DNS_RDATASETATTR_CACHE; rdataset->trust = dns_trust_glue; } if (type == dns_rdatatype_soa) { /* * SOA, or RRSIG SOA. * * Only one SOA is allowed. */ if (rdataset->type == dns_rdatatype_soa) { if (soa_name != NULL && name != soa_name) { log_formerr(fctx, "multiple SOA " "RRs in " "authority " "section"); return (DNS_R_FORMERR); } soa_name = name; } name->attributes |= DNS_NAMEATTR_NCACHE; rdataset->attributes |= DNS_RDATASETATTR_NCACHE; if (aa) rdataset->trust = dns_trust_authauthority; else if (ISFORWARDER(fctx->addrinfo)) rdataset->trust = dns_trust_answer; else rdataset->trust = dns_trust_additional; } } } nextname: result = dns_message_nextname(message, section); if (result == ISC_R_NOMORE) break; else if (result != ISC_R_SUCCESS) return (result); } log_ns_ttl(fctx, "noanswer_response"); if (ns_rdataset != NULL && dns_name_equal(&fctx->domain, ns_name) && !dns_name_equal(ns_name, dns_rootname)) trim_ns_ttl(fctx, ns_name, ns_rdataset); /* * A negative response has a SOA record (Type 2) * and a optional NS RRset (Type 1) or it has neither * a SOA or a NS RRset (Type 3, handled above) or * rcode is NXDOMAIN (handled above) in which case * the NS RRset is allowed (Type 4). */ if (soa_name != NULL) negative_response = ISC_TRUE; result = dns_message_firstname(message, section); while (result == ISC_R_SUCCESS) { name = NULL; dns_message_currentname(message, section, &name); if (dns_name_issubdomain(name, &fctx->domain)) { for (rdataset = ISC_LIST_HEAD(name->list); rdataset != NULL; rdataset = ISC_LIST_NEXT(rdataset, link)) { type = rdataset->type; if (type == dns_rdatatype_rrsig) type = rdataset->covers; if (type == dns_rdatatype_nsec || type == dns_rdatatype_nsec3) { /* * NSEC or RRSIG NSEC. */ if (negative_response) { name->attributes |= DNS_NAMEATTR_NCACHE; rdataset->attributes |= DNS_RDATASETATTR_NCACHE; } else if (type == dns_rdatatype_nsec) { name->attributes |= DNS_NAMEATTR_CACHE; rdataset->attributes |= DNS_RDATASETATTR_CACHE; } if (aa) rdataset->trust = dns_trust_authauthority; else if (ISFORWARDER(fctx->addrinfo)) rdataset->trust = dns_trust_answer; else rdataset->trust = dns_trust_additional; /* * No additional data needs to be * marked. */ } else if (type == dns_rdatatype_ds) { /* * DS or SIG DS. * * These should only be here if * this is a referral, and there * should only be one DS RRset. */ if (ns_name == NULL) { log_formerr(fctx, "DS with no " "referral"); return (DNS_R_FORMERR); } if (rdataset->type == dns_rdatatype_ds) { if (ds_name != NULL && name != ds_name) { log_formerr(fctx, "DS doesn't " "match " "referral " "(NS)"); return (DNS_R_FORMERR); } ds_name = name; } name->attributes |= DNS_NAMEATTR_CACHE; rdataset->attributes |= DNS_RDATASETATTR_CACHE; if (aa) rdataset->trust = dns_trust_authauthority; else if (ISFORWARDER(fctx->addrinfo)) rdataset->trust = dns_trust_answer; else rdataset->trust = dns_trust_additional; } } } else { save_name = name; save_type = ISC_LIST_HEAD(name->list)->type; } result = dns_message_nextname(message, section); if (result == ISC_R_NOMORE) break; else if (result != ISC_R_SUCCESS) return (result); } /* * Trigger lookups for DNS nameservers. */ if (negative_response && message->rcode == dns_rcode_noerror && fctx->type == dns_rdatatype_ds && soa_name != NULL && dns_name_equal(soa_name, qname) && !dns_name_equal(qname, dns_rootname)) return (DNS_R_CHASEDSSERVERS); /* * Did we find anything? */ if (!negative_response && ns_name == NULL) { /* * Nope. */ if (oqname != NULL) { /* * We've already got a partial CNAME/DNAME chain, * and haven't found else anything useful here, but * no error has occurred since we have an answer. */ return (ISC_R_SUCCESS); } else { /* * The responder is insane. */ if (save_name == NULL) { log_formerr(fctx, "invalid response"); return (DNS_R_FORMERR); } if (!dns_name_issubdomain(save_name, &fctx->domain)) { char nbuf[DNS_NAME_FORMATSIZE]; char dbuf[DNS_NAME_FORMATSIZE]; char tbuf[DNS_RDATATYPE_FORMATSIZE]; dns_rdatatype_format(save_type, tbuf, sizeof(tbuf)); dns_name_format(save_name, nbuf, sizeof(nbuf)); dns_name_format(&fctx->domain, dbuf, sizeof(dbuf)); log_formerr(fctx, "Name %s (%s) not subdomain" " of zone %s -- invalid response", nbuf, tbuf, dbuf); } else { log_formerr(fctx, "invalid response"); } return (DNS_R_FORMERR); } } /* * If we found both NS and SOA, they should be the same name. */ if (ns_name != NULL && soa_name != NULL && ns_name != soa_name) { log_formerr(fctx, "NS/SOA mismatch"); return (DNS_R_FORMERR); } /* * Do we have a referral? (We only want to follow a referral if * we're not following a chain.) */ if (!negative_response && ns_name != NULL && oqname == NULL) { /* * We already know ns_name is a subdomain of fctx->domain. * If ns_name is equal to fctx->domain, we're not making * progress. We return DNS_R_FORMERR so that we'll keep * trying other servers. */ if (dns_name_equal(ns_name, &fctx->domain)) { log_formerr(fctx, "non-improving referral"); return (DNS_R_FORMERR); } /* * If the referral name is not a parent of the query * name, consider the responder insane. */ if (! dns_name_issubdomain(&fctx->name, ns_name)) { /* Logged twice */ log_formerr(fctx, "referral to non-parent"); FCTXTRACE("referral to non-parent"); return (DNS_R_FORMERR); } /* * Mark any additional data related to this rdataset. * It's important that we do this before we change the * query domain. */ INSIST(ns_rdataset != NULL); fctx->attributes |= FCTX_ATTR_GLUING; (void)dns_rdataset_additionaldata(ns_rdataset, check_related, fctx); #if CHECK_FOR_GLUE_IN_ANSWER /* * Look in the answer section for "glue" that is incorrectly * returned as a answer. This is needed if the server also * minimizes the response size by not adding records to the * additional section that are in the answer section or if * the record gets dropped due to message size constraints. */ if ((look_in_options & LOOK_FOR_GLUE_IN_ANSWER) != 0 && (fctx->type == dns_rdatatype_aaaa || fctx->type == dns_rdatatype_a)) (void)dns_rdataset_additionaldata(ns_rdataset, check_answer, fctx); #endif fctx->attributes &= ~FCTX_ATTR_GLUING; /* * NS rdatasets with 0 TTL cause problems. * dns_view_findzonecut() will not find them when we * try to follow the referral, and we'll SERVFAIL * because the best nameservers are now above QDOMAIN. * We force the TTL to 1 second to prevent this. */ if (ns_rdataset->ttl == 0) ns_rdataset->ttl = 1; /* * Set the current query domain to the referral name. * * XXXRTH We should check if we're in forward-only mode, and * if so we should bail out. */ INSIST(dns_name_countlabels(&fctx->domain) > 0); dns_name_free(&fctx->domain, fctx->mctx); if (dns_rdataset_isassociated(&fctx->nameservers)) dns_rdataset_disassociate(&fctx->nameservers); dns_name_init(&fctx->domain, NULL); result = dns_name_dup(ns_name, fctx->mctx, &fctx->domain); if (result != ISC_R_SUCCESS) return (result); fctx->attributes |= FCTX_ATTR_WANTCACHE; fctx->ns_ttl_ok = ISC_FALSE; log_ns_ttl(fctx, "DELEGATION"); return (DNS_R_DELEGATION); } /* * Since we're not doing a referral, we don't want to cache any * NS RRs we may have found. */ if (ns_name != NULL) ns_name->attributes &= ~DNS_NAMEATTR_CACHE; if (negative_response && oqname == NULL) fctx->attributes |= FCTX_ATTR_WANTNCACHE; return (ISC_R_SUCCESS); } static isc_result_t answer_response(fetchctx_t *fctx) { isc_result_t result; dns_message_t *message; dns_name_t *name, *qname, tname, *ns_name; dns_rdataset_t *rdataset, *ns_rdataset; isc_boolean_t done, external, chaining, aa, found, want_chaining; isc_boolean_t have_answer, found_cname, found_type, wanted_chaining; unsigned int aflag; dns_rdatatype_t type; dns_fixedname_t dname, fqname; dns_view_t *view; FCTXTRACE("answer_response"); message = fctx->rmessage; /* * Examine the answer section, marking those rdatasets which are * part of the answer and should be cached. */ done = ISC_FALSE; found_cname = ISC_FALSE; found_type = ISC_FALSE; chaining = ISC_FALSE; have_answer = ISC_FALSE; want_chaining = ISC_FALSE; POST(want_chaining); if ((message->flags & DNS_MESSAGEFLAG_AA) != 0) aa = ISC_TRUE; else aa = ISC_FALSE; qname = &fctx->name; type = fctx->type; view = fctx->res->view; result = dns_message_firstname(message, DNS_SECTION_ANSWER); while (!done && result == ISC_R_SUCCESS) { name = NULL; dns_message_currentname(message, DNS_SECTION_ANSWER, &name); external = ISC_TF(!dns_name_issubdomain(name, &fctx->domain)); if (dns_name_equal(name, qname)) { wanted_chaining = ISC_FALSE; for (rdataset = ISC_LIST_HEAD(name->list); rdataset != NULL; rdataset = ISC_LIST_NEXT(rdataset, link)) { found = ISC_FALSE; want_chaining = ISC_FALSE; aflag = 0; if (rdataset->type == dns_rdatatype_nsec3) { /* * NSEC3 records are not allowed to * appear in the answer section. */ log_formerr(fctx, "NSEC3 in answer"); return (DNS_R_FORMERR); } /* * Apply filters, if given, on answers to reject * a malicious attempt of rebinding. */ if ((rdataset->type == dns_rdatatype_a || rdataset->type == dns_rdatatype_aaaa) && !is_answeraddress_allowed(view, name, rdataset)) { return (DNS_R_SERVFAIL); } if (rdataset->type == type && !found_cname) { /* * We've found an ordinary answer. */ found = ISC_TRUE; found_type = ISC_TRUE; done = ISC_TRUE; aflag = DNS_RDATASETATTR_ANSWER; } else if (type == dns_rdatatype_any) { /* * We've found an answer matching * an ANY query. There may be * more. */ found = ISC_TRUE; aflag = DNS_RDATASETATTR_ANSWER; } else if (rdataset->type == dns_rdatatype_rrsig && rdataset->covers == type && !found_cname) { /* * We've found a signature that * covers the type we're looking for. */ found = ISC_TRUE; found_type = ISC_TRUE; aflag = DNS_RDATASETATTR_ANSWERSIG; } else if (rdataset->type == dns_rdatatype_cname && !found_type) { /* * We're looking for something else, * but we found a CNAME. * * Getting a CNAME response for some * query types is an error, see * RFC 4035, Section 2.5. */ if (type == dns_rdatatype_rrsig || type == dns_rdatatype_key || type == dns_rdatatype_nsec) { char buf[DNS_RDATATYPE_FORMATSIZE]; dns_rdatatype_format(fctx->type, buf, sizeof(buf)); log_formerr(fctx, "CNAME response " "for %s RR", buf); return (DNS_R_FORMERR); } found = ISC_TRUE; found_cname = ISC_TRUE; want_chaining = ISC_TRUE; aflag = DNS_RDATASETATTR_ANSWER; result = cname_target(rdataset, &tname); if (result != ISC_R_SUCCESS) return (result); /* Apply filters on the target name. */ if (!is_answertarget_allowed(view, name, rdataset->type, &tname, &fctx->domain)) { return (DNS_R_SERVFAIL); } } else if (rdataset->type == dns_rdatatype_rrsig && rdataset->covers == dns_rdatatype_cname && !found_type) { /* * We're looking for something else, * but we found a SIG CNAME. */ found = ISC_TRUE; found_cname = ISC_TRUE; aflag = DNS_RDATASETATTR_ANSWERSIG; } if (found) { /* * We've found an answer to our * question. */ name->attributes |= DNS_NAMEATTR_CACHE; rdataset->attributes |= DNS_RDATASETATTR_CACHE; rdataset->trust = dns_trust_answer; if (!chaining) { /* * This data is "the" answer * to our question only if * we're not chaining (i.e. * if we haven't followed * a CNAME or DNAME). */ INSIST(!external); if (aflag == DNS_RDATASETATTR_ANSWER) have_answer = ISC_TRUE; name->attributes |= DNS_NAMEATTR_ANSWER; rdataset->attributes |= aflag; if (aa) rdataset->trust = dns_trust_authanswer; } else if (external) { /* * This data is outside of * our query domain, and * may not be cached. */ rdataset->attributes |= DNS_RDATASETATTR_EXTERNAL; } /* * Mark any additional data related * to this rdataset. */ (void)dns_rdataset_additionaldata( rdataset, check_related, fctx); /* * CNAME chaining. */ if (want_chaining) { wanted_chaining = ISC_TRUE; name->attributes |= DNS_NAMEATTR_CHAINING; rdataset->attributes |= DNS_RDATASETATTR_CHAINING; qname = &tname; } } /* * We could add an "else" clause here and * log that we're ignoring this rdataset. */ } /* * If wanted_chaining is true, we've done * some chaining as the result of processing * this node, and thus we need to set * chaining to true. * * We don't set chaining inside of the * rdataset loop because doing that would * cause us to ignore the signatures of * CNAMEs. */ if (wanted_chaining) chaining = ISC_TRUE; } else { /* * Look for a DNAME (or its SIG). Anything else is * ignored. */ wanted_chaining = ISC_FALSE; for (rdataset = ISC_LIST_HEAD(name->list); rdataset != NULL; rdataset = ISC_LIST_NEXT(rdataset, link)) { isc_boolean_t found_dname = ISC_FALSE; dns_name_t *dname_name; found = ISC_FALSE; aflag = 0; if (rdataset->type == dns_rdatatype_dname) { /* * We're looking for something else, * but we found a DNAME. * * If we're not chaining, then the * DNAME should not be external. */ if (!chaining && external) { log_formerr(fctx, "external DNAME"); return (DNS_R_FORMERR); } found = ISC_TRUE; want_chaining = ISC_TRUE; POST(want_chaining); aflag = DNS_RDATASETATTR_ANSWER; result = dname_target(fctx, rdataset, qname, name, &dname); if (result == ISC_R_NOSPACE) { /* * We can't construct the * DNAME target. Do not * try to continue. */ want_chaining = ISC_FALSE; POST(want_chaining); } else if (result != ISC_R_SUCCESS) return (result); else found_dname = ISC_TRUE; dname_name = dns_fixedname_name(&dname); if (!is_answertarget_allowed(view, qname, rdataset->type, dname_name, &fctx->domain)) { return (DNS_R_SERVFAIL); } } else if (rdataset->type == dns_rdatatype_rrsig && rdataset->covers == dns_rdatatype_dname) { /* * We've found a signature that * covers the DNAME. */ found = ISC_TRUE; aflag = DNS_RDATASETATTR_ANSWERSIG; } if (found) { /* * We've found an answer to our * question. */ name->attributes |= DNS_NAMEATTR_CACHE; rdataset->attributes |= DNS_RDATASETATTR_CACHE; rdataset->trust = dns_trust_answer; if (!chaining) { /* * This data is "the" answer * to our question only if * we're not chaining. */ INSIST(!external); if (aflag == DNS_RDATASETATTR_ANSWER) have_answer = ISC_TRUE; name->attributes |= DNS_NAMEATTR_ANSWER; rdataset->attributes |= aflag; if (aa) rdataset->trust = dns_trust_authanswer; } else if (external) { rdataset->attributes |= DNS_RDATASETATTR_EXTERNAL; } /* * DNAME chaining. */ if (found_dname) { /* * Copy the dname into the * qname fixed name. * * Although we check for * failure of the copy * operation, in practice it * should never fail since * we already know that the * result fits in a fixedname. */ dns_fixedname_init(&fqname); result = dns_name_copy( dns_fixedname_name(&dname), dns_fixedname_name(&fqname), NULL); if (result != ISC_R_SUCCESS) return (result); wanted_chaining = ISC_TRUE; name->attributes |= DNS_NAMEATTR_CHAINING; rdataset->attributes |= DNS_RDATASETATTR_CHAINING; qname = dns_fixedname_name( &fqname); } } } if (wanted_chaining) chaining = ISC_TRUE; } result = dns_message_nextname(message, DNS_SECTION_ANSWER); } if (result == ISC_R_NOMORE) result = ISC_R_SUCCESS; if (result != ISC_R_SUCCESS) return (result); /* * We should have found an answer. */ if (!have_answer) { log_formerr(fctx, "reply has no answer"); return (DNS_R_FORMERR); } /* * This response is now potentially cacheable. */ fctx->attributes |= FCTX_ATTR_WANTCACHE; /* * Did chaining end before we got the final answer? */ if (chaining) { /* * Yes. This may be a negative reply, so hand off * authority section processing to the noanswer code. * If it isn't a noanswer response, no harm will be * done. */ return (noanswer_response(fctx, qname, 0)); } /* * We didn't end with an incomplete chain, so the rcode should be * "no error". */ if (message->rcode != dns_rcode_noerror) { log_formerr(fctx, "CNAME/DNAME chain complete, but RCODE " "indicates error"); return (DNS_R_FORMERR); } /* * Examine the authority section (if there is one). * * We expect there to be only one owner name for all the rdatasets * in this section, and we expect that it is not external. */ done = ISC_FALSE; ns_name = NULL; ns_rdataset = NULL; result = dns_message_firstname(message, DNS_SECTION_AUTHORITY); while (!done && result == ISC_R_SUCCESS) { name = NULL; dns_message_currentname(message, DNS_SECTION_AUTHORITY, &name); external = ISC_TF(!dns_name_issubdomain(name, &fctx->domain)); if (!external) { /* * We expect to find NS or SIG NS rdatasets, and * nothing else. */ for (rdataset = ISC_LIST_HEAD(name->list); rdataset != NULL; rdataset = ISC_LIST_NEXT(rdataset, link)) { if (rdataset->type == dns_rdatatype_ns || (rdataset->type == dns_rdatatype_rrsig && rdataset->covers == dns_rdatatype_ns)) { name->attributes |= DNS_NAMEATTR_CACHE; rdataset->attributes |= DNS_RDATASETATTR_CACHE; if (aa && !chaining) rdataset->trust = dns_trust_authauthority; else rdataset->trust = dns_trust_additional; if (rdataset->type == dns_rdatatype_ns) { ns_name = name; ns_rdataset = rdataset; } /* * Mark any additional data related * to this rdataset. */ (void)dns_rdataset_additionaldata( rdataset, check_related, fctx); done = ISC_TRUE; } } } result = dns_message_nextname(message, DNS_SECTION_AUTHORITY); } if (result == ISC_R_NOMORE) result = ISC_R_SUCCESS; log_ns_ttl(fctx, "answer_response"); if (ns_rdataset != NULL && dns_name_equal(&fctx->domain, ns_name) && !dns_name_equal(ns_name, dns_rootname)) trim_ns_ttl(fctx, ns_name, ns_rdataset); return (result); } static isc_boolean_t fctx_decreference(fetchctx_t *fctx) { isc_boolean_t bucket_empty = ISC_FALSE; INSIST(fctx->references > 0); fctx->references--; if (fctx->references == 0) { /* * No one cares about the result of this fetch anymore. */ if (fctx->pending == 0 && fctx->nqueries == 0 && ISC_LIST_EMPTY(fctx->validators) && SHUTTINGDOWN(fctx)) { /* * This fctx is already shutdown; we were just * waiting for the last reference to go away. */ bucket_empty = fctx_unlink(fctx); fctx_destroy(fctx); } else { /* * Initiate shutdown. */ fctx_shutdown(fctx); } } return (bucket_empty); } static void resume_dslookup(isc_task_t *task, isc_event_t *event) { dns_fetchevent_t *fevent; dns_resolver_t *res; fetchctx_t *fctx; isc_result_t result; isc_boolean_t bucket_empty; isc_boolean_t locked = ISC_FALSE; unsigned int bucketnum; dns_rdataset_t nameservers; dns_fixedname_t fixed; dns_name_t *domain; REQUIRE(event->ev_type == DNS_EVENT_FETCHDONE); fevent = (dns_fetchevent_t *)event; fctx = event->ev_arg; REQUIRE(VALID_FCTX(fctx)); res = fctx->res; UNUSED(task); FCTXTRACE("resume_dslookup"); if (fevent->node != NULL) dns_db_detachnode(fevent->db, &fevent->node); if (fevent->db != NULL) dns_db_detach(&fevent->db); dns_rdataset_init(&nameservers); bucketnum = fctx->bucketnum; if (fevent->result == ISC_R_CANCELED) { dns_resolver_destroyfetch(&fctx->nsfetch); fctx_done(fctx, ISC_R_CANCELED, __LINE__); } else if (fevent->result == ISC_R_SUCCESS) { FCTXTRACE("resuming DS lookup"); dns_resolver_destroyfetch(&fctx->nsfetch); if (dns_rdataset_isassociated(&fctx->nameservers)) dns_rdataset_disassociate(&fctx->nameservers); dns_rdataset_clone(fevent->rdataset, &fctx->nameservers); fctx->ns_ttl = fctx->nameservers.ttl; fctx->ns_ttl_ok = ISC_TRUE; log_ns_ttl(fctx, "resume_dslookup"); dns_name_free(&fctx->domain, fctx->mctx); dns_name_init(&fctx->domain, NULL); result = dns_name_dup(&fctx->nsname, fctx->mctx, &fctx->domain); if (result != ISC_R_SUCCESS) { fctx_done(fctx, DNS_R_SERVFAIL, __LINE__); goto cleanup; } /* * Try again. */ fctx_try(fctx, ISC_TRUE, ISC_FALSE); } else { unsigned int n; dns_rdataset_t *nsrdataset = NULL; /* * Retrieve state from fctx->nsfetch before we destroy it. */ dns_fixedname_init(&fixed); domain = dns_fixedname_name(&fixed); dns_name_copy(&fctx->nsfetch->private->domain, domain, NULL); if (dns_name_equal(&fctx->nsname, domain)) { fctx_done(fctx, DNS_R_SERVFAIL, __LINE__); dns_resolver_destroyfetch(&fctx->nsfetch); goto cleanup; } if (dns_rdataset_isassociated( &fctx->nsfetch->private->nameservers)) { dns_rdataset_clone( &fctx->nsfetch->private->nameservers, &nameservers); nsrdataset = &nameservers; } else domain = NULL; dns_resolver_destroyfetch(&fctx->nsfetch); n = dns_name_countlabels(&fctx->nsname); dns_name_getlabelsequence(&fctx->nsname, 1, n - 1, &fctx->nsname); if (dns_rdataset_isassociated(fevent->rdataset)) dns_rdataset_disassociate(fevent->rdataset); FCTXTRACE("continuing to look for parent's NS records"); result = dns_resolver_createfetch(fctx->res, &fctx->nsname, dns_rdatatype_ns, domain, nsrdataset, NULL, 0, task, resume_dslookup, fctx, &fctx->nsrrset, NULL, &fctx->nsfetch); if (result != ISC_R_SUCCESS) fctx_done(fctx, result, __LINE__); else { LOCK(&res->buckets[bucketnum].lock); locked = ISC_TRUE; fctx->references++; } } cleanup: if (dns_rdataset_isassociated(&nameservers)) dns_rdataset_disassociate(&nameservers); if (dns_rdataset_isassociated(fevent->rdataset)) dns_rdataset_disassociate(fevent->rdataset); INSIST(fevent->sigrdataset == NULL); isc_event_free(&event); if (!locked) LOCK(&res->buckets[bucketnum].lock); bucket_empty = fctx_decreference(fctx); UNLOCK(&res->buckets[bucketnum].lock); if (bucket_empty) empty_bucket(res); } static inline void checknamessection(dns_message_t *message, dns_section_t section) { isc_result_t result; dns_name_t *name; dns_rdata_t rdata = DNS_RDATA_INIT; dns_rdataset_t *rdataset; for (result = dns_message_firstname(message, section); result == ISC_R_SUCCESS; result = dns_message_nextname(message, section)) { name = NULL; dns_message_currentname(message, section, &name); for (rdataset = ISC_LIST_HEAD(name->list); rdataset != NULL; rdataset = ISC_LIST_NEXT(rdataset, link)) { for (result = dns_rdataset_first(rdataset); result == ISC_R_SUCCESS; result = dns_rdataset_next(rdataset)) { dns_rdataset_current(rdataset, &rdata); if (!dns_rdata_checkowner(name, rdata.rdclass, rdata.type, ISC_FALSE) || !dns_rdata_checknames(&rdata, name, NULL)) { rdataset->attributes |= DNS_RDATASETATTR_CHECKNAMES; } dns_rdata_reset(&rdata); } } } } static void checknames(dns_message_t *message) { checknamessection(message, DNS_SECTION_ANSWER); checknamessection(message, DNS_SECTION_AUTHORITY); checknamessection(message, DNS_SECTION_ADDITIONAL); } /* * Log server NSID at log level 'level' */ static void log_nsid(isc_buffer_t *opt, size_t nsid_len, resquery_t *query, int level, isc_mem_t *mctx) { static const char hex[17] = "0123456789abcdef"; char addrbuf[ISC_SOCKADDR_FORMATSIZE]; isc_uint16_t buflen, i; unsigned char *p, *buf, *nsid; /* Allocate buffer for storing hex version of the NSID */ buflen = (isc_uint16_t)nsid_len * 2 + 1; buf = isc_mem_get(mctx, buflen); if (buf == NULL) return; /* Convert to hex */ p = buf; nsid = isc_buffer_current(opt); for (i = 0; i < nsid_len; i++) { *p++ = hex[(nsid[0] >> 4) & 0xf]; *p++ = hex[nsid[0] & 0xf]; nsid++; } *p = '\0'; isc_sockaddr_format(&query->addrinfo->sockaddr, addrbuf, sizeof(addrbuf)); isc_log_write(dns_lctx, DNS_LOGCATEGORY_RESOLVER, DNS_LOGMODULE_RESOLVER, level, "received NSID '%s' from %s", buf, addrbuf); /* Clean up */ isc_mem_put(mctx, buf, buflen); return; } static void log_packet(dns_message_t *message, int level, isc_mem_t *mctx) { isc_buffer_t buffer; char *buf = NULL; int len = 1024; isc_result_t result; if (! isc_log_wouldlog(dns_lctx, level)) return; /* * Note that these are multiline debug messages. We want a newline * to appear in the log after each message. */ do { buf = isc_mem_get(mctx, len); if (buf == NULL) break; isc_buffer_init(&buffer, buf, len); result = dns_message_totext(message, &dns_master_style_debug, 0, &buffer); if (result == ISC_R_NOSPACE) { isc_mem_put(mctx, buf, len); len += 1024; } else if (result == ISC_R_SUCCESS) isc_log_write(dns_lctx, DNS_LOGCATEGORY_RESOLVER, DNS_LOGMODULE_RESOLVER, level, "received packet:\n%.*s", (int)isc_buffer_usedlength(&buffer), buf); } while (result == ISC_R_NOSPACE); if (buf != NULL) isc_mem_put(mctx, buf, len); } static isc_boolean_t iscname(fetchctx_t *fctx) { isc_result_t result; result = dns_message_findname(fctx->rmessage, DNS_SECTION_ANSWER, &fctx->name, dns_rdatatype_cname, 0, NULL, NULL); return (result == ISC_R_SUCCESS ? ISC_TRUE : ISC_FALSE); } static isc_boolean_t betterreferral(fetchctx_t *fctx) { isc_result_t result; dns_name_t *name; dns_rdataset_t *rdataset; dns_message_t *message = fctx->rmessage; for (result = dns_message_firstname(message, DNS_SECTION_AUTHORITY); result == ISC_R_SUCCESS; result = dns_message_nextname(message, DNS_SECTION_AUTHORITY)) { name = NULL; dns_message_currentname(message, DNS_SECTION_AUTHORITY, &name); if (!isstrictsubdomain(name, &fctx->domain)) continue; for (rdataset = ISC_LIST_HEAD(name->list); rdataset != NULL; rdataset = ISC_LIST_NEXT(rdataset, link)) if (rdataset->type == dns_rdatatype_ns) return (ISC_TRUE); } return (ISC_FALSE); } static void process_opt(resquery_t *query, dns_rdataset_t *opt) { dns_rdata_t rdata; isc_buffer_t optbuf; isc_result_t result; isc_uint16_t optcode; isc_uint16_t optlen; result = dns_rdataset_first(opt); if (result == ISC_R_SUCCESS) { dns_rdata_init(&rdata); dns_rdataset_current(opt, &rdata); isc_buffer_init(&optbuf, rdata.data, rdata.length); isc_buffer_add(&optbuf, rdata.length); while (isc_buffer_remaininglength(&optbuf) >= 4) { optcode = isc_buffer_getuint16(&optbuf); optlen = isc_buffer_getuint16(&optbuf); INSIST(optlen <= isc_buffer_remaininglength(&optbuf)); switch (optcode) { case DNS_OPT_NSID: if (query->options & DNS_FETCHOPT_WANTNSID) log_nsid(&optbuf, optlen, query, ISC_LOG_INFO, query->fctx->res->mctx); isc_buffer_forward(&optbuf, optlen); break; default: isc_buffer_forward(&optbuf, optlen); break; } } INSIST(isc_buffer_remaininglength(&optbuf) == 0U); } } static void resquery_response(isc_task_t *task, isc_event_t *event) { isc_result_t result = ISC_R_SUCCESS; resquery_t *query = event->ev_arg; dns_dispatchevent_t *devent = (dns_dispatchevent_t *)event; isc_boolean_t keep_trying, get_nameservers, resend; isc_boolean_t truncated; dns_message_t *message; dns_rdataset_t *opt; fetchctx_t *fctx; dns_name_t *fname; dns_fixedname_t foundname; isc_stdtime_t now; isc_time_t tnow, *finish; dns_adbaddrinfo_t *addrinfo; unsigned int options; unsigned int findoptions; isc_result_t broken_server; badnstype_t broken_type = badns_response; isc_boolean_t no_response; REQUIRE(VALID_QUERY(query)); fctx = query->fctx; options = query->options; REQUIRE(VALID_FCTX(fctx)); REQUIRE(event->ev_type == DNS_EVENT_DISPATCH); QTRACE("response"); if (isc_sockaddr_pf(&query->addrinfo->sockaddr) == PF_INET) inc_stats(fctx->res, dns_resstatscounter_responsev4); else inc_stats(fctx->res, dns_resstatscounter_responsev6); (void)isc_timer_touch(fctx->timer); keep_trying = ISC_FALSE; broken_server = ISC_R_SUCCESS; get_nameservers = ISC_FALSE; resend = ISC_FALSE; truncated = ISC_FALSE; finish = NULL; no_response = ISC_FALSE; if (fctx->res->exiting) { result = ISC_R_SHUTTINGDOWN; goto done; } fctx->timeouts = 0; fctx->timeout = ISC_FALSE; fctx->addrinfo = query->addrinfo; /* * XXXRTH We should really get the current time just once. We * need a routine to convert from an isc_time_t to an * isc_stdtime_t. */ TIME_NOW(&tnow); finish = &tnow; isc_stdtime_get(&now); /* * Did the dispatcher have a problem? */ if (devent->result != ISC_R_SUCCESS) { if (devent->result == ISC_R_EOF && (query->options & DNS_FETCHOPT_NOEDNS0) == 0) { /* * The problem might be that they * don't understand EDNS0. Turn it * off and try again. */ options |= DNS_FETCHOPT_NOEDNS0; resend = ISC_TRUE; add_bad_edns(fctx, &query->addrinfo->sockaddr); } else { /* * There's no hope for this query. */ keep_trying = ISC_TRUE; /* * If this is a network error on an exclusive query * socket, mark the server as bad so that we won't try * it for this fetch again. Also adjust finish and * no_response so that we penalize this address in SRTT * adjustment later. */ if (query->exclusivesocket && (devent->result == ISC_R_HOSTUNREACH || devent->result == ISC_R_NETUNREACH || devent->result == ISC_R_CONNREFUSED || devent->result == ISC_R_CANCELED)) { broken_server = devent->result; broken_type = badns_unreachable; finish = NULL; no_response = ISC_TRUE; } } goto done; } message = fctx->rmessage; if (query->tsig != NULL) { result = dns_message_setquerytsig(message, query->tsig); if (result != ISC_R_SUCCESS) goto done; } if (query->tsigkey) { result = dns_message_settsigkey(message, query->tsigkey); if (result != ISC_R_SUCCESS) goto done; } result = dns_message_parse(message, &devent->buffer, 0); if (result != ISC_R_SUCCESS) { switch (result) { case ISC_R_UNEXPECTEDEND: if (!message->question_ok || (message->flags & DNS_MESSAGEFLAG_TC) == 0 || (options & DNS_FETCHOPT_TCP) != 0) { /* * Either the message ended prematurely, * and/or wasn't marked as being truncated, * and/or this is a response to a query we * sent over TCP. In all of these cases, * something is wrong with the remote * server and we don't want to retry using * TCP. */ if ((query->options & DNS_FETCHOPT_NOEDNS0) == 0) { /* * The problem might be that they * don't understand EDNS0. Turn it * off and try again. */ options |= DNS_FETCHOPT_NOEDNS0; resend = ISC_TRUE; add_bad_edns(fctx, &query->addrinfo->sockaddr); inc_stats(fctx->res, dns_resstatscounter_edns0fail); } else { broken_server = result; keep_trying = ISC_TRUE; } goto done; } /* * We defer retrying via TCP for a bit so we can * check out this message further. */ truncated = ISC_TRUE; break; case DNS_R_FORMERR: if ((query->options & DNS_FETCHOPT_NOEDNS0) == 0) { /* * The problem might be that they * don't understand EDNS0. Turn it * off and try again. */ options |= DNS_FETCHOPT_NOEDNS0; resend = ISC_TRUE; add_bad_edns(fctx, &query->addrinfo->sockaddr); inc_stats(fctx->res, dns_resstatscounter_edns0fail); } else { broken_server = DNS_R_UNEXPECTEDRCODE; keep_trying = ISC_TRUE; } goto done; default: /* * Something bad has happened. */ goto done; } } /* * Log the incoming packet. */ log_packet(message, ISC_LOG_DEBUG(10), fctx->res->mctx); /* * Process receive opt record. */ opt = dns_message_getopt(message); if (opt != NULL) process_opt(query, opt); /* * If the message is signed, check the signature. If not, this * returns success anyway. */ result = dns_message_checksig(message, fctx->res->view); if (result != ISC_R_SUCCESS) goto done; /* * The dispatcher should ensure we only get responses with QR set. */ INSIST((message->flags & DNS_MESSAGEFLAG_QR) != 0); /* * INSIST() that the message comes from the place we sent it to, * since the dispatch code should ensure this. * * INSIST() that the message id is correct (this should also be * ensured by the dispatch code). */ /* * We have an affirmative response to the query and we have * previously got a response from this server which indicated * EDNS may not be supported so we can now cache the lack of * EDNS support. */ if (opt == NULL && !EDNSOK(query->addrinfo) && (message->rcode == dns_rcode_noerror || message->rcode == dns_rcode_nxdomain || message->rcode == dns_rcode_refused || message->rcode == dns_rcode_yxdomain) && bad_edns(fctx, &query->addrinfo->sockaddr)) { dns_adb_changeflags(fctx->adb, query->addrinfo, DNS_FETCHOPT_NOEDNS0, DNS_FETCHOPT_NOEDNS0); } /* * If we get a non error EDNS response record the fact so we * won't fallback to plain DNS in the future for this server. */ if (opt != NULL && !EDNSOK(query->addrinfo) && (query->options & DNS_FETCHOPT_NOEDNS0) == 0 && (message->rcode == dns_rcode_noerror || message->rcode == dns_rcode_nxdomain || message->rcode == dns_rcode_refused || message->rcode == dns_rcode_yxdomain)) { dns_adb_changeflags(fctx->adb, query->addrinfo, FCTX_ADDRINFO_EDNSOK, FCTX_ADDRINFO_EDNSOK); } /* * Deal with truncated responses by retrying using TCP. */ if ((message->flags & DNS_MESSAGEFLAG_TC) != 0) truncated = ISC_TRUE; if (truncated) { inc_stats(fctx->res, dns_resstatscounter_truncated); if ((options & DNS_FETCHOPT_TCP) != 0) { broken_server = DNS_R_TRUNCATEDTCP; keep_trying = ISC_TRUE; } else { options |= DNS_FETCHOPT_TCP; resend = ISC_TRUE; } goto done; } /* * Is it a query response? */ if (message->opcode != dns_opcode_query) { /* XXXRTH Log */ broken_server = DNS_R_UNEXPECTEDOPCODE; keep_trying = ISC_TRUE; goto done; } /* * Update statistics about erroneous responses. */ if (message->rcode != dns_rcode_noerror) { switch (message->rcode) { case dns_rcode_nxdomain: inc_stats(fctx->res, dns_resstatscounter_nxdomain); break; case dns_rcode_servfail: inc_stats(fctx->res, dns_resstatscounter_servfail); break; case dns_rcode_formerr: inc_stats(fctx->res, dns_resstatscounter_formerr); break; default: inc_stats(fctx->res, dns_resstatscounter_othererror); break; } } /* * Is the remote server broken, or does it dislike us? */ if (message->rcode != dns_rcode_noerror && message->rcode != dns_rcode_nxdomain) { if (((message->rcode == dns_rcode_formerr || message->rcode == dns_rcode_notimp) || (message->rcode == dns_rcode_servfail && dns_message_getopt(message) == NULL)) && (query->options & DNS_FETCHOPT_NOEDNS0) == 0) { /* * It's very likely they don't like EDNS0. * If the response code is SERVFAIL, also check if the * response contains an OPT RR and don't cache the * failure since it can be returned for various other * reasons. * * XXXRTH We should check if the question * we're asking requires EDNS0, and * if so, we should bail out. */ options |= DNS_FETCHOPT_NOEDNS0; resend = ISC_TRUE; /* * Remember that they may not like EDNS0. */ add_bad_edns(fctx, &query->addrinfo->sockaddr); inc_stats(fctx->res, dns_resstatscounter_edns0fail); } else if (message->rcode == dns_rcode_formerr) { if (ISFORWARDER(query->addrinfo)) { /* * This forwarder doesn't understand us, * but other forwarders might. Keep trying. */ broken_server = DNS_R_REMOTEFORMERR; keep_trying = ISC_TRUE; } else { /* * The server doesn't understand us. Since * all servers for a zone need similar * capabilities, we assume that we will get * FORMERR from all servers, and thus we * cannot make any more progress with this * fetch. */ log_formerr(fctx, "server sent FORMERR"); result = DNS_R_FORMERR; } } else if (message->rcode == dns_rcode_yxdomain) { /* * DNAME mapping failed because the new name * was too long. There's no chance of success * for this fetch. */ result = DNS_R_YXDOMAIN; } else if (message->rcode == dns_rcode_badvers) { unsigned int flags, mask; unsigned int version; resend = ISC_TRUE; INSIST(opt != NULL); version = (opt->ttl >> 16) & 0xff; flags = (version << DNS_FETCHOPT_EDNSVERSIONSHIFT) | DNS_FETCHOPT_EDNSVERSIONSET; mask = DNS_FETCHOPT_EDNSVERSIONMASK | DNS_FETCHOPT_EDNSVERSIONSET; /* * Record that we got a good EDNS response. */ if (query->ednsversion > (int)version && !EDNSOK(query->addrinfo)) { dns_adb_changeflags(fctx->adb, query->addrinfo, FCTX_ADDRINFO_EDNSOK, FCTX_ADDRINFO_EDNSOK); } /* * Record the supported EDNS version. */ switch (version) { case 0: dns_adb_changeflags(fctx->adb, query->addrinfo, flags, mask); break; default: broken_server = DNS_R_BADVERS; keep_trying = ISC_TRUE; break; } } else { /* * XXXRTH log. */ broken_server = DNS_R_UNEXPECTEDRCODE; INSIST(broken_server != ISC_R_SUCCESS); keep_trying = ISC_TRUE; } goto done; } /* * Is the question the same as the one we asked? */ result = same_question(fctx); if (result != ISC_R_SUCCESS) { /* XXXRTH Log */ if (result == DNS_R_FORMERR) keep_trying = ISC_TRUE; goto done; } /* * Is the server lame? */ if (fctx->res->lame_ttl != 0 && !ISFORWARDER(query->addrinfo) && is_lame(fctx)) { inc_stats(fctx->res, dns_resstatscounter_lame); log_lame(fctx, query->addrinfo); result = dns_adb_marklame(fctx->adb, query->addrinfo, &fctx->name, fctx->type, now + fctx->res->lame_ttl); if (result != ISC_R_SUCCESS) isc_log_write(dns_lctx, DNS_LOGCATEGORY_RESOLVER, DNS_LOGMODULE_RESOLVER, ISC_LOG_ERROR, "could not mark server as lame: %s", isc_result_totext(result)); broken_server = DNS_R_LAME; keep_trying = ISC_TRUE; goto done; } /* * Enforce delegations only zones like NET and COM. */ if (!ISFORWARDER(query->addrinfo) && dns_view_isdelegationonly(fctx->res->view, &fctx->domain) && !dns_name_equal(&fctx->domain, &fctx->name) && fix_mustbedelegationornxdomain(message, fctx)) { char namebuf[DNS_NAME_FORMATSIZE]; char domainbuf[DNS_NAME_FORMATSIZE]; char addrbuf[ISC_SOCKADDR_FORMATSIZE]; char classbuf[64]; char typebuf[64]; dns_name_format(&fctx->name, namebuf, sizeof(namebuf)); dns_name_format(&fctx->domain, domainbuf, sizeof(domainbuf)); dns_rdatatype_format(fctx->type, typebuf, sizeof(typebuf)); dns_rdataclass_format(fctx->res->rdclass, classbuf, sizeof(classbuf)); isc_sockaddr_format(&query->addrinfo->sockaddr, addrbuf, sizeof(addrbuf)); isc_log_write(dns_lctx, DNS_LOGCATEGORY_DELEGATION_ONLY, DNS_LOGMODULE_RESOLVER, ISC_LOG_NOTICE, "enforced delegation-only for '%s' (%s/%s/%s) " "from %s", domainbuf, namebuf, typebuf, classbuf, addrbuf); } if ((fctx->res->options & DNS_RESOLVER_CHECKNAMES) != 0) checknames(message); /* * Clear cache bits. */ fctx->attributes &= ~(FCTX_ATTR_WANTNCACHE | FCTX_ATTR_WANTCACHE); /* * Did we get any answers? */ if (message->counts[DNS_SECTION_ANSWER] > 0 && (message->rcode == dns_rcode_noerror || message->rcode == dns_rcode_nxdomain)) { /* * [normal case] * We've got answers. If it has an authoritative answer or an * answer from a forwarder, we're done. */ if ((message->flags & DNS_MESSAGEFLAG_AA) != 0 || ISFORWARDER(query->addrinfo)) result = answer_response(fctx); else if (iscname(fctx) && fctx->type != dns_rdatatype_any && fctx->type != dns_rdatatype_cname) { /* * A BIND8 server could return a non-authoritative * answer when a CNAME is followed. We should treat * it as a valid answer. */ result = answer_response(fctx); } else if (fctx->type != dns_rdatatype_ns && !betterreferral(fctx)) { /* * Lame response !!!. */ result = answer_response(fctx); } else { if (fctx->type == dns_rdatatype_ns) { /* * A BIND 8 server could incorrectly return a * non-authoritative answer to an NS query * instead of a referral. Since this answer * lacks the SIGs necessary to do DNSSEC * validation, we must invoke the following * special kludge to treat it as a referral. */ result = noanswer_response(fctx, NULL, LOOK_FOR_NS_IN_ANSWER); } else { /* * Some other servers may still somehow include * an answer when it should return a referral * with an empty answer. Check to see if we can * treat this as a referral by ignoring the * answer. Further more, there may be an * implementation that moves A/AAAA glue records * to the answer section for that type of * delegation when the query is for that glue * record. LOOK_FOR_GLUE_IN_ANSWER will handle * such a corner case. */ result = noanswer_response(fctx, NULL, LOOK_FOR_GLUE_IN_ANSWER); } if (result != DNS_R_DELEGATION) { /* * At this point, AA is not set, the response * is not a referral, and the server is not a * forwarder. It is technically lame and it's * easier to treat it as such than to figure out * some more elaborate course of action. */ broken_server = DNS_R_LAME; keep_trying = ISC_TRUE; goto done; } goto force_referral; } if (result != ISC_R_SUCCESS) { if (result == DNS_R_FORMERR) keep_trying = ISC_TRUE; goto done; } } else if (message->counts[DNS_SECTION_AUTHORITY] > 0 || message->rcode == dns_rcode_noerror || message->rcode == dns_rcode_nxdomain) { /* * NXDOMAIN, NXRDATASET, or referral. */ result = noanswer_response(fctx, NULL, 0); switch (result) { case ISC_R_SUCCESS: case DNS_R_CHASEDSSERVERS: break; case DNS_R_DELEGATION: force_referral: /* * We don't have the answer, but we know a better * place to look. */ get_nameservers = ISC_TRUE; keep_trying = ISC_TRUE; /* * We have a new set of name servers, and it * has not experienced any restarts yet. */ fctx->restarts = 0; /* * Update local statistics counters collected for each * new zone. */ fctx->referrals++; fctx->querysent = 0; fctx->lamecount = 0; fctx->neterr = 0; fctx->badresp = 0; fctx->adberr = 0; result = ISC_R_SUCCESS; break; default: /* * Something has gone wrong. */ if (result == DNS_R_FORMERR) keep_trying = ISC_TRUE; goto done; } } else { /* * The server is insane. */ /* XXXRTH Log */ broken_server = DNS_R_UNEXPECTEDRCODE; keep_trying = ISC_TRUE; goto done; } /* * Follow additional section data chains. */ chase_additional(fctx); /* * Cache the cacheable parts of the message. This may also cause * work to be queued to the DNSSEC validator. */ if (WANTCACHE(fctx)) { result = cache_message(fctx, query->addrinfo, now); if (result != ISC_R_SUCCESS) goto done; } /* * Ncache the negatively cacheable parts of the message. This may * also cause work to be queued to the DNSSEC validator. */ if (WANTNCACHE(fctx)) { dns_rdatatype_t covers; /* * Cache DS NXDOMAIN seperately to other types. */ if (message->rcode == dns_rcode_nxdomain && fctx->type != dns_rdatatype_ds) covers = dns_rdatatype_any; else covers = fctx->type; /* * Cache any negative cache entries in the message. */ result = ncache_message(fctx, query->addrinfo, covers, now); } done: /* * Remember the query's addrinfo, in case we need to mark the * server as broken. */ addrinfo = query->addrinfo; /* * Cancel the query. * * XXXRTH Don't cancel the query if waiting for validation? */ fctx_cancelquery(&query, &devent, finish, no_response); if (keep_trying) { if (result == DNS_R_FORMERR) broken_server = DNS_R_FORMERR; if (broken_server != ISC_R_SUCCESS) { /* * Add this server to the list of bad servers for * this fctx. */ add_bad(fctx, addrinfo, broken_server, broken_type); } if (get_nameservers) { dns_name_t *name; dns_fixedname_init(&foundname); fname = dns_fixedname_name(&foundname); if (result != ISC_R_SUCCESS) { fctx_done(fctx, DNS_R_SERVFAIL, __LINE__); return; } findoptions = 0; if (dns_rdatatype_atparent(fctx->type)) findoptions |= DNS_DBFIND_NOEXACT; if ((options & DNS_FETCHOPT_UNSHARED) == 0) name = &fctx->name; else name = &fctx->domain; result = dns_view_findzonecut(fctx->res->view, name, fname, now, findoptions, ISC_TRUE, &fctx->nameservers, NULL); if (result != ISC_R_SUCCESS) { FCTXTRACE("couldn't find a zonecut"); fctx_done(fctx, DNS_R_SERVFAIL, __LINE__); return; } if (!dns_name_issubdomain(fname, &fctx->domain)) { /* * The best nameservers are now above our * QDOMAIN. */ FCTXTRACE("nameservers now above QDOMAIN"); fctx_done(fctx, DNS_R_SERVFAIL, __LINE__); return; } dns_name_free(&fctx->domain, fctx->mctx); dns_name_init(&fctx->domain, NULL); result = dns_name_dup(fname, fctx->mctx, &fctx->domain); if (result != ISC_R_SUCCESS) { fctx_done(fctx, DNS_R_SERVFAIL, __LINE__); return; } fctx->ns_ttl = fctx->nameservers.ttl; fctx->ns_ttl_ok = ISC_TRUE; fctx_cancelqueries(fctx, ISC_TRUE); fctx_cleanupfinds(fctx); fctx_cleanupaltfinds(fctx); fctx_cleanupforwaddrs(fctx); fctx_cleanupaltaddrs(fctx); } /* * Try again. */ fctx_try(fctx, !get_nameservers, ISC_FALSE); } else if (resend) { /* * Resend (probably with changed options). */ FCTXTRACE("resend"); inc_stats(fctx->res, dns_resstatscounter_retry); result = fctx_query(fctx, addrinfo, options); if (result != ISC_R_SUCCESS) fctx_done(fctx, result, __LINE__); } else if (result == ISC_R_SUCCESS && !HAVE_ANSWER(fctx)) { /* * All has gone well so far, but we are waiting for the * DNSSEC validator to validate the answer. */ FCTXTRACE("wait for validator"); fctx_cancelqueries(fctx, ISC_TRUE); /* * We must not retransmit while the validator is working; * it has references to the current rmessage. */ result = fctx_stopidletimer(fctx); if (result != ISC_R_SUCCESS) fctx_done(fctx, result, __LINE__); } else if (result == DNS_R_CHASEDSSERVERS) { unsigned int n; add_bad(fctx, addrinfo, result, broken_type); fctx_cancelqueries(fctx, ISC_TRUE); fctx_cleanupfinds(fctx); fctx_cleanupforwaddrs(fctx); n = dns_name_countlabels(&fctx->name); dns_name_getlabelsequence(&fctx->name, 1, n - 1, &fctx->nsname); FCTXTRACE("suspending DS lookup to find parent's NS records"); result = dns_resolver_createfetch(fctx->res, &fctx->nsname, dns_rdatatype_ns, NULL, NULL, NULL, 0, task, resume_dslookup, fctx, &fctx->nsrrset, NULL, &fctx->nsfetch); if (result != ISC_R_SUCCESS) fctx_done(fctx, result, __LINE__); else { LOCK(&fctx->res->buckets[fctx->bucketnum].lock); fctx->references++; UNLOCK(&fctx->res->buckets[fctx->bucketnum].lock); result = fctx_stopidletimer(fctx); if (result != ISC_R_SUCCESS) fctx_done(fctx, result, __LINE__); } } else { /* * We're done. */ fctx_done(fctx, result, __LINE__); } } /*** *** Resolver Methods ***/ static void destroy_badcache(dns_resolver_t *res) { dns_badcache_t *bad, *next; unsigned int i; if (res->badcache != NULL) { for (i = 0; i < res->badhash; i++) for (bad = res->badcache[i]; bad != NULL; bad = next) { next = bad->next; isc_mem_put(res->mctx, bad, sizeof(*bad) + bad->name.length); res->badcount--; } isc_mem_put(res->mctx, res->badcache, sizeof(*res->badcache) * res->badhash); res->badcache = NULL; res->badhash = 0; INSIST(res->badcount == 0); } } static void destroy(dns_resolver_t *res) { unsigned int i; alternate_t *a; REQUIRE(res->references == 0); REQUIRE(!res->priming); REQUIRE(res->primefetch == NULL); RTRACE("destroy"); INSIST(res->nfctx == 0); DESTROYLOCK(&res->primelock); DESTROYLOCK(&res->nlock); DESTROYLOCK(&res->lock); for (i = 0; i < res->nbuckets; i++) { INSIST(ISC_LIST_EMPTY(res->buckets[i].fctxs)); isc_task_shutdown(res->buckets[i].task); isc_task_detach(&res->buckets[i].task); DESTROYLOCK(&res->buckets[i].lock); isc_mem_detach(&res->buckets[i].mctx); } isc_mem_put(res->mctx, res->buckets, res->nbuckets * sizeof(fctxbucket_t)); if (res->dispatches4 != NULL) dns_dispatchset_destroy(&res->dispatches4); if (res->dispatches6 != NULL) dns_dispatchset_destroy(&res->dispatches6); while ((a = ISC_LIST_HEAD(res->alternates)) != NULL) { ISC_LIST_UNLINK(res->alternates, a, link); if (!a->isaddress) dns_name_free(&a->_u._n.name, res->mctx); isc_mem_put(res->mctx, a, sizeof(*a)); } dns_resolver_reset_algorithms(res); destroy_badcache(res); dns_resolver_resetmustbesecure(res); #if USE_ALGLOCK isc_rwlock_destroy(&res->alglock); #endif #if USE_MBSLOCK isc_rwlock_destroy(&res->mbslock); #endif isc_timer_detach(&res->spillattimer); res->magic = 0; isc_mem_put(res->mctx, res, sizeof(*res)); } static void send_shutdown_events(dns_resolver_t *res) { isc_event_t *event, *next_event; isc_task_t *etask; /* * Caller must be holding the resolver lock. */ for (event = ISC_LIST_HEAD(res->whenshutdown); event != NULL; event = next_event) { next_event = ISC_LIST_NEXT(event, ev_link); ISC_LIST_UNLINK(res->whenshutdown, event, ev_link); etask = event->ev_sender; event->ev_sender = res; isc_task_sendanddetach(&etask, &event); } } static void empty_bucket(dns_resolver_t *res) { RTRACE("empty_bucket"); LOCK(&res->lock); INSIST(res->activebuckets > 0); res->activebuckets--; if (res->activebuckets == 0) send_shutdown_events(res); UNLOCK(&res->lock); } static void spillattimer_countdown(isc_task_t *task, isc_event_t *event) { dns_resolver_t *res = event->ev_arg; isc_result_t result; unsigned int count; isc_boolean_t logit = ISC_FALSE; REQUIRE(VALID_RESOLVER(res)); UNUSED(task); LOCK(&res->lock); INSIST(!res->exiting); if (res->spillat > res->spillatmin) { res->spillat--; logit = ISC_TRUE; } if (res->spillat <= res->spillatmin) { result = isc_timer_reset(res->spillattimer, isc_timertype_inactive, NULL, NULL, ISC_TRUE); RUNTIME_CHECK(result == ISC_R_SUCCESS); } count = res->spillat; UNLOCK(&res->lock); if (logit) isc_log_write(dns_lctx, DNS_LOGCATEGORY_RESOLVER, DNS_LOGMODULE_RESOLVER, ISC_LOG_NOTICE, "clients-per-query decreased to %u", count); isc_event_free(&event); } isc_result_t dns_resolver_create(dns_view_t *view, isc_taskmgr_t *taskmgr, unsigned int ntasks, unsigned int ndisp, isc_socketmgr_t *socketmgr, isc_timermgr_t *timermgr, unsigned int options, dns_dispatchmgr_t *dispatchmgr, dns_dispatch_t *dispatchv4, dns_dispatch_t *dispatchv6, dns_resolver_t **resp) { dns_resolver_t *res; isc_result_t result = ISC_R_SUCCESS; unsigned int i, buckets_created = 0; isc_task_t *task = NULL; char name[16]; unsigned dispattr; /* * Create a resolver. */ REQUIRE(DNS_VIEW_VALID(view)); REQUIRE(ntasks > 0); REQUIRE(ndisp > 0); REQUIRE(resp != NULL && *resp == NULL); REQUIRE(dispatchmgr != NULL); REQUIRE(dispatchv4 != NULL || dispatchv6 != NULL); res = isc_mem_get(view->mctx, sizeof(*res)); if (res == NULL) return (ISC_R_NOMEMORY); RTRACE("create"); res->mctx = view->mctx; res->rdclass = view->rdclass; res->socketmgr = socketmgr; res->timermgr = timermgr; res->taskmgr = taskmgr; res->dispatchmgr = dispatchmgr; res->view = view; res->options = options; res->lame_ttl = 0; ISC_LIST_INIT(res->alternates); res->udpsize = RECV_BUFFER_SIZE; res->algorithms = NULL; res->badcache = NULL; res->badcount = 0; res->badhash = 0; res->badsweep = 0; res->mustbesecure = NULL; res->spillatmin = res->spillat = 10; res->spillatmax = 100; res->spillattimer = NULL; res->zero_no_soa_ttl = ISC_FALSE; res->query_timeout = DEFAULT_QUERY_TIMEOUT; res->maxdepth = DEFAULT_RECURSION_DEPTH; res->maxqueries = DEFAULT_MAX_QUERIES; res->nbuckets = ntasks; res->activebuckets = ntasks; res->buckets = isc_mem_get(view->mctx, ntasks * sizeof(fctxbucket_t)); if (res->buckets == NULL) { result = ISC_R_NOMEMORY; goto cleanup_res; } for (i = 0; i < ntasks; i++) { result = isc_mutex_init(&res->buckets[i].lock); if (result != ISC_R_SUCCESS) goto cleanup_buckets; res->buckets[i].task = NULL; result = isc_task_create(taskmgr, 0, &res->buckets[i].task); if (result != ISC_R_SUCCESS) { DESTROYLOCK(&res->buckets[i].lock); goto cleanup_buckets; } res->buckets[i].mctx = NULL; snprintf(name, sizeof(name), "res%u", i); #ifdef ISC_PLATFORM_USETHREADS /* * Use a separate memory context for each bucket to reduce * contention among multiple threads. Do this only when * enabling threads because it will be require more memory. */ result = isc_mem_create(0, 0, &res->buckets[i].mctx); if (result != ISC_R_SUCCESS) { isc_task_detach(&res->buckets[i].task); DESTROYLOCK(&res->buckets[i].lock); goto cleanup_buckets; } isc_mem_setname(res->buckets[i].mctx, name, NULL); #else isc_mem_attach(view->mctx, &res->buckets[i].mctx); #endif isc_task_setname(res->buckets[i].task, name, res); ISC_LIST_INIT(res->buckets[i].fctxs); res->buckets[i].exiting = ISC_FALSE; buckets_created++; } res->dispatches4 = NULL; if (dispatchv4 != NULL) { dns_dispatchset_create(view->mctx, socketmgr, taskmgr, dispatchv4, &res->dispatches4, ndisp); dispattr = dns_dispatch_getattributes(dispatchv4); res->exclusivev4 = ISC_TF((dispattr & DNS_DISPATCHATTR_EXCLUSIVE) != 0); } res->dispatches6 = NULL; if (dispatchv6 != NULL) { dns_dispatchset_create(view->mctx, socketmgr, taskmgr, dispatchv6, &res->dispatches6, ndisp); dispattr = dns_dispatch_getattributes(dispatchv6); res->exclusivev6 = ISC_TF((dispattr & DNS_DISPATCHATTR_EXCLUSIVE) != 0); } res->references = 1; res->exiting = ISC_FALSE; res->frozen = ISC_FALSE; ISC_LIST_INIT(res->whenshutdown); res->priming = ISC_FALSE; res->primefetch = NULL; res->nfctx = 0; result = isc_mutex_init(&res->lock); if (result != ISC_R_SUCCESS) goto cleanup_dispatches; result = isc_mutex_init(&res->nlock); if (result != ISC_R_SUCCESS) goto cleanup_lock; result = isc_mutex_init(&res->primelock); if (result != ISC_R_SUCCESS) goto cleanup_nlock; task = NULL; result = isc_task_create(taskmgr, 0, &task); if (result != ISC_R_SUCCESS) goto cleanup_primelock; result = isc_timer_create(timermgr, isc_timertype_inactive, NULL, NULL, task, spillattimer_countdown, res, &res->spillattimer); isc_task_detach(&task); if (result != ISC_R_SUCCESS) goto cleanup_primelock; #if USE_ALGLOCK result = isc_rwlock_init(&res->alglock, 0, 0); if (result != ISC_R_SUCCESS) goto cleanup_spillattimer; #endif #if USE_MBSLOCK result = isc_rwlock_init(&res->mbslock, 0, 0); if (result != ISC_R_SUCCESS) goto cleanup_alglock; #endif res->magic = RES_MAGIC; *resp = res; return (ISC_R_SUCCESS); #if USE_MBSLOCK cleanup_alglock: #if USE_ALGLOCK isc_rwlock_destroy(&res->alglock); #endif #endif #if USE_ALGLOCK || USE_MBSLOCK cleanup_spillattimer: isc_timer_detach(&res->spillattimer); #endif cleanup_primelock: DESTROYLOCK(&res->primelock); cleanup_nlock: DESTROYLOCK(&res->nlock); cleanup_lock: DESTROYLOCK(&res->lock); cleanup_dispatches: if (res->dispatches6 != NULL) dns_dispatchset_destroy(&res->dispatches6); if (res->dispatches4 != NULL) dns_dispatchset_destroy(&res->dispatches4); cleanup_buckets: for (i = 0; i < buckets_created; i++) { isc_mem_detach(&res->buckets[i].mctx); DESTROYLOCK(&res->buckets[i].lock); isc_task_shutdown(res->buckets[i].task); isc_task_detach(&res->buckets[i].task); } isc_mem_put(view->mctx, res->buckets, res->nbuckets * sizeof(fctxbucket_t)); cleanup_res: isc_mem_put(view->mctx, res, sizeof(*res)); return (result); } #ifdef BIND9 static void prime_done(isc_task_t *task, isc_event_t *event) { dns_resolver_t *res; dns_fetchevent_t *fevent; dns_fetch_t *fetch; dns_db_t *db = NULL; REQUIRE(event->ev_type == DNS_EVENT_FETCHDONE); fevent = (dns_fetchevent_t *)event; res = event->ev_arg; REQUIRE(VALID_RESOLVER(res)); UNUSED(task); LOCK(&res->lock); INSIST(res->priming); res->priming = ISC_FALSE; LOCK(&res->primelock); fetch = res->primefetch; res->primefetch = NULL; UNLOCK(&res->primelock); UNLOCK(&res->lock); if (fevent->result == ISC_R_SUCCESS && res->view->cache != NULL && res->view->hints != NULL) { dns_cache_attachdb(res->view->cache, &db); dns_root_checkhints(res->view, res->view->hints, db); dns_db_detach(&db); } if (fevent->node != NULL) dns_db_detachnode(fevent->db, &fevent->node); if (fevent->db != NULL) dns_db_detach(&fevent->db); if (dns_rdataset_isassociated(fevent->rdataset)) dns_rdataset_disassociate(fevent->rdataset); INSIST(fevent->sigrdataset == NULL); isc_mem_put(res->mctx, fevent->rdataset, sizeof(*fevent->rdataset)); isc_event_free(&event); dns_resolver_destroyfetch(&fetch); } void dns_resolver_prime(dns_resolver_t *res) { isc_boolean_t want_priming = ISC_FALSE; dns_rdataset_t *rdataset; isc_result_t result; REQUIRE(VALID_RESOLVER(res)); REQUIRE(res->frozen); RTRACE("dns_resolver_prime"); LOCK(&res->lock); if (!res->exiting && !res->priming) { INSIST(res->primefetch == NULL); res->priming = ISC_TRUE; want_priming = ISC_TRUE; } UNLOCK(&res->lock); if (want_priming) { /* * To avoid any possible recursive locking problems, we * start the priming fetch like any other fetch, and holding * no resolver locks. No one else will try to start it * because we're the ones who set res->priming to true. * Any other callers of dns_resolver_prime() while we're * running will see that res->priming is already true and * do nothing. */ RTRACE("priming"); rdataset = isc_mem_get(res->mctx, sizeof(*rdataset)); if (rdataset == NULL) { LOCK(&res->lock); INSIST(res->priming); INSIST(res->primefetch == NULL); res->priming = ISC_FALSE; UNLOCK(&res->lock); return; } dns_rdataset_init(rdataset); LOCK(&res->primelock); result = dns_resolver_createfetch(res, dns_rootname, dns_rdatatype_ns, NULL, NULL, NULL, 0, res->buckets[0].task, prime_done, res, rdataset, NULL, &res->primefetch); UNLOCK(&res->primelock); if (result != ISC_R_SUCCESS) { LOCK(&res->lock); INSIST(res->priming); res->priming = ISC_FALSE; UNLOCK(&res->lock); } } } #endif /* BIND9 */ void dns_resolver_freeze(dns_resolver_t *res) { /* * Freeze resolver. */ REQUIRE(VALID_RESOLVER(res)); res->frozen = ISC_TRUE; } void dns_resolver_attach(dns_resolver_t *source, dns_resolver_t **targetp) { REQUIRE(VALID_RESOLVER(source)); REQUIRE(targetp != NULL && *targetp == NULL); RRTRACE(source, "attach"); LOCK(&source->lock); REQUIRE(!source->exiting); INSIST(source->references > 0); source->references++; INSIST(source->references != 0); UNLOCK(&source->lock); *targetp = source; } void dns_resolver_whenshutdown(dns_resolver_t *res, isc_task_t *task, isc_event_t **eventp) { isc_task_t *clone; isc_event_t *event; REQUIRE(VALID_RESOLVER(res)); REQUIRE(eventp != NULL); event = *eventp; *eventp = NULL; LOCK(&res->lock); if (res->exiting && res->activebuckets == 0) { /* * We're already shutdown. Send the event. */ event->ev_sender = res; isc_task_send(task, &event); } else { clone = NULL; isc_task_attach(task, &clone); event->ev_sender = clone; ISC_LIST_APPEND(res->whenshutdown, event, ev_link); } UNLOCK(&res->lock); } void dns_resolver_shutdown(dns_resolver_t *res) { unsigned int i; fetchctx_t *fctx; isc_result_t result; REQUIRE(VALID_RESOLVER(res)); RTRACE("shutdown"); LOCK(&res->lock); if (!res->exiting) { RTRACE("exiting"); res->exiting = ISC_TRUE; for (i = 0; i < res->nbuckets; i++) { LOCK(&res->buckets[i].lock); for (fctx = ISC_LIST_HEAD(res->buckets[i].fctxs); fctx != NULL; fctx = ISC_LIST_NEXT(fctx, link)) fctx_shutdown(fctx); if (res->dispatches4 != NULL && !res->exclusivev4) { dns_dispatchset_cancelall(res->dispatches4, res->buckets[i].task); } if (res->dispatches6 != NULL && !res->exclusivev6) { dns_dispatchset_cancelall(res->dispatches6, res->buckets[i].task); } res->buckets[i].exiting = ISC_TRUE; if (ISC_LIST_EMPTY(res->buckets[i].fctxs)) { INSIST(res->activebuckets > 0); res->activebuckets--; } UNLOCK(&res->buckets[i].lock); } if (res->activebuckets == 0) send_shutdown_events(res); result = isc_timer_reset(res->spillattimer, isc_timertype_inactive, NULL, NULL, ISC_TRUE); RUNTIME_CHECK(result == ISC_R_SUCCESS); } UNLOCK(&res->lock); } void dns_resolver_detach(dns_resolver_t **resp) { dns_resolver_t *res; isc_boolean_t need_destroy = ISC_FALSE; REQUIRE(resp != NULL); res = *resp; REQUIRE(VALID_RESOLVER(res)); RTRACE("detach"); LOCK(&res->lock); INSIST(res->references > 0); res->references--; if (res->references == 0) { INSIST(res->exiting && res->activebuckets == 0); need_destroy = ISC_TRUE; } UNLOCK(&res->lock); if (need_destroy) destroy(res); *resp = NULL; } static inline isc_boolean_t fctx_match(fetchctx_t *fctx, dns_name_t *name, dns_rdatatype_t type, unsigned int options) { /* * Don't match fetch contexts that are shutting down. */ if (fctx->cloned || fctx->state == fetchstate_done || ISC_LIST_EMPTY(fctx->events)) return (ISC_FALSE); if (fctx->type != type || fctx->options != options) return (ISC_FALSE); return (dns_name_equal(&fctx->name, name)); } static inline void log_fetch(dns_name_t *name, dns_rdatatype_t type) { char namebuf[DNS_NAME_FORMATSIZE]; char typebuf[DNS_RDATATYPE_FORMATSIZE]; int level = ISC_LOG_DEBUG(1); if (! isc_log_wouldlog(dns_lctx, level)) return; dns_name_format(name, namebuf, sizeof(namebuf)); dns_rdatatype_format(type, typebuf, sizeof(typebuf)); isc_log_write(dns_lctx, DNS_LOGCATEGORY_RESOLVER, DNS_LOGMODULE_RESOLVER, level, "createfetch: %s %s", namebuf, typebuf); } isc_result_t dns_resolver_createfetch(dns_resolver_t *res, dns_name_t *name, dns_rdatatype_t type, dns_name_t *domain, dns_rdataset_t *nameservers, dns_forwarders_t *forwarders, unsigned int options, isc_task_t *task, isc_taskaction_t action, void *arg, dns_rdataset_t *rdataset, dns_rdataset_t *sigrdataset, dns_fetch_t **fetchp) { return (dns_resolver_createfetch3(res, name, type, domain, nameservers, forwarders, NULL, 0, options, 0, NULL, task, action, arg, rdataset, sigrdataset, fetchp)); } isc_result_t dns_resolver_createfetch2(dns_resolver_t *res, dns_name_t *name, dns_rdatatype_t type, dns_name_t *domain, dns_rdataset_t *nameservers, dns_forwarders_t *forwarders, isc_sockaddr_t *client, dns_messageid_t id, unsigned int options, isc_task_t *task, isc_taskaction_t action, void *arg, dns_rdataset_t *rdataset, dns_rdataset_t *sigrdataset, dns_fetch_t **fetchp) { return (dns_resolver_createfetch3(res, name, type, domain, nameservers, forwarders, client, id, options, 0, NULL, task, action, arg, rdataset, sigrdataset, fetchp)); } isc_result_t dns_resolver_createfetch3(dns_resolver_t *res, dns_name_t *name, dns_rdatatype_t type, dns_name_t *domain, dns_rdataset_t *nameservers, dns_forwarders_t *forwarders, isc_sockaddr_t *client, dns_messageid_t id, unsigned int options, unsigned int depth, isc_counter_t *qc, isc_task_t *task, isc_taskaction_t action, void *arg, dns_rdataset_t *rdataset, dns_rdataset_t *sigrdataset, dns_fetch_t **fetchp) { dns_fetch_t *fetch; fetchctx_t *fctx = NULL; isc_result_t result = ISC_R_SUCCESS; unsigned int bucketnum; isc_boolean_t new_fctx = ISC_FALSE; isc_event_t *event; unsigned int count = 0; unsigned int spillat; unsigned int spillatmin; isc_boolean_t destroy = ISC_FALSE; UNUSED(forwarders); REQUIRE(VALID_RESOLVER(res)); REQUIRE(res->frozen); /* XXXRTH Check for meta type */ if (domain != NULL) { REQUIRE(DNS_RDATASET_VALID(nameservers)); REQUIRE(nameservers->type == dns_rdatatype_ns); } else REQUIRE(nameservers == NULL); REQUIRE(forwarders == NULL); REQUIRE(!dns_rdataset_isassociated(rdataset)); REQUIRE(sigrdataset == NULL || !dns_rdataset_isassociated(sigrdataset)); REQUIRE(fetchp != NULL && *fetchp == NULL); log_fetch(name, type); /* * XXXRTH use a mempool? */ fetch = isc_mem_get(res->mctx, sizeof(*fetch)); if (fetch == NULL) return (ISC_R_NOMEMORY); fetch->mctx = NULL; isc_mem_attach(res->mctx, &fetch->mctx); bucketnum = dns_name_fullhash(name, ISC_FALSE) % res->nbuckets; LOCK(&res->lock); spillat = res->spillat; spillatmin = res->spillatmin; UNLOCK(&res->lock); LOCK(&res->buckets[bucketnum].lock); if (res->buckets[bucketnum].exiting) { result = ISC_R_SHUTTINGDOWN; goto unlock; } if ((options & DNS_FETCHOPT_UNSHARED) == 0) { for (fctx = ISC_LIST_HEAD(res->buckets[bucketnum].fctxs); fctx != NULL; fctx = ISC_LIST_NEXT(fctx, link)) { if (fctx_match(fctx, name, type, options)) break; } } /* * Is this a duplicate? */ if (fctx != NULL && client != NULL) { dns_fetchevent_t *fevent; for (fevent = ISC_LIST_HEAD(fctx->events); fevent != NULL; fevent = ISC_LIST_NEXT(fevent, ev_link)) { if (fevent->client != NULL && fevent->id == id && isc_sockaddr_equal(fevent->client, client)) { result = DNS_R_DUPLICATE; goto unlock; } count++; } } if (count >= spillatmin && spillatmin != 0) { INSIST(fctx != NULL); if (count >= spillat) fctx->spilled = ISC_TRUE; if (fctx->spilled) { result = DNS_R_DROP; goto unlock; } } if (fctx == NULL) { result = fctx_create(res, name, type, domain, nameservers, options, bucketnum, depth, qc, &fctx); if (result != ISC_R_SUCCESS) goto unlock; new_fctx = ISC_TRUE; } else if (fctx->depth > depth) fctx->depth = depth; result = fctx_join(fctx, task, client, id, action, arg, rdataset, sigrdataset, fetch); if (new_fctx) { if (result == ISC_R_SUCCESS) { /* * Launch this fctx. */ event = &fctx->control_event; ISC_EVENT_INIT(event, sizeof(*event), 0, NULL, DNS_EVENT_FETCHCONTROL, fctx_start, fctx, NULL, NULL, NULL); isc_task_send(res->buckets[bucketnum].task, &event); } else { /* * We don't care about the result of fctx_unlink() * since we know we're not exiting. */ (void)fctx_unlink(fctx); destroy = ISC_TRUE; } } unlock: UNLOCK(&res->buckets[bucketnum].lock); if (destroy) fctx_destroy(fctx); if (result == ISC_R_SUCCESS) { FTRACE("created"); *fetchp = fetch; } else isc_mem_putanddetach(&fetch->mctx, fetch, sizeof(*fetch)); return (result); } void dns_resolver_cancelfetch(dns_fetch_t *fetch) { fetchctx_t *fctx; dns_resolver_t *res; dns_fetchevent_t *event, *next_event; isc_task_t *etask; REQUIRE(DNS_FETCH_VALID(fetch)); fctx = fetch->private; REQUIRE(VALID_FCTX(fctx)); res = fctx->res; FTRACE("cancelfetch"); LOCK(&res->buckets[fctx->bucketnum].lock); /* * Find the completion event for this fetch (as opposed * to those for other fetches that have joined the same * fctx) and send it with result = ISC_R_CANCELED. */ event = NULL; if (fctx->state != fetchstate_done) { for (event = ISC_LIST_HEAD(fctx->events); event != NULL; event = next_event) { next_event = ISC_LIST_NEXT(event, ev_link); if (event->fetch == fetch) { ISC_LIST_UNLINK(fctx->events, event, ev_link); break; } } } if (event != NULL) { etask = event->ev_sender; event->ev_sender = fctx; event->result = ISC_R_CANCELED; isc_task_sendanddetach(&etask, ISC_EVENT_PTR(&event)); } /* * The fctx continues running even if no fetches remain; * the answer is still cached. */ UNLOCK(&res->buckets[fctx->bucketnum].lock); } void dns_resolver_destroyfetch(dns_fetch_t **fetchp) { dns_fetch_t *fetch; dns_resolver_t *res; dns_fetchevent_t *event, *next_event; fetchctx_t *fctx; unsigned int bucketnum; isc_boolean_t bucket_empty; REQUIRE(fetchp != NULL); fetch = *fetchp; REQUIRE(DNS_FETCH_VALID(fetch)); fctx = fetch->private; REQUIRE(VALID_FCTX(fctx)); res = fctx->res; FTRACE("destroyfetch"); bucketnum = fctx->bucketnum; LOCK(&res->buckets[bucketnum].lock); /* * Sanity check: the caller should have gotten its event before * trying to destroy the fetch. */ event = NULL; if (fctx->state != fetchstate_done) { for (event = ISC_LIST_HEAD(fctx->events); event != NULL; event = next_event) { next_event = ISC_LIST_NEXT(event, ev_link); RUNTIME_CHECK(event->fetch != fetch); } } bucket_empty = fctx_decreference(fctx); UNLOCK(&res->buckets[bucketnum].lock); isc_mem_putanddetach(&fetch->mctx, fetch, sizeof(*fetch)); *fetchp = NULL; if (bucket_empty) empty_bucket(res); } void dns_resolver_logfetch(dns_fetch_t *fetch, isc_log_t *lctx, isc_logcategory_t *category, isc_logmodule_t *module, int level, isc_boolean_t duplicateok) { fetchctx_t *fctx; dns_resolver_t *res; char domainbuf[DNS_NAME_FORMATSIZE]; REQUIRE(DNS_FETCH_VALID(fetch)); fctx = fetch->private; REQUIRE(VALID_FCTX(fctx)); res = fctx->res; LOCK(&res->buckets[fctx->bucketnum].lock); INSIST(fctx->exitline >= 0); if (!fctx->logged || duplicateok) { dns_name_format(&fctx->domain, domainbuf, sizeof(domainbuf)); isc_log_write(lctx, category, module, level, "fetch completed at %s:%d for %s in " "%" ISC_PRINT_QUADFORMAT "u." "%06" ISC_PRINT_QUADFORMAT "u: %s/%s " "[domain:%s,referral:%u,restart:%u,qrysent:%u," "timeout:%u,lame:%u,neterr:%u,badresp:%u," "adberr:%u,findfail:%u,valfail:%u]", __FILE__, fctx->exitline, fctx->info, fctx->duration / US_PER_SEC, fctx->duration % US_PER_SEC, isc_result_totext(fctx->result), isc_result_totext(fctx->vresult), domainbuf, fctx->referrals, fctx->restarts, fctx->querysent, fctx->timeouts, fctx->lamecount, fctx->neterr, fctx->badresp, fctx->adberr, fctx->findfail, fctx->valfail); fctx->logged = ISC_TRUE; } UNLOCK(&res->buckets[fctx->bucketnum].lock); } dns_dispatchmgr_t * dns_resolver_dispatchmgr(dns_resolver_t *resolver) { REQUIRE(VALID_RESOLVER(resolver)); return (resolver->dispatchmgr); } dns_dispatch_t * dns_resolver_dispatchv4(dns_resolver_t *resolver) { REQUIRE(VALID_RESOLVER(resolver)); return (dns_dispatchset_get(resolver->dispatches4)); } dns_dispatch_t * dns_resolver_dispatchv6(dns_resolver_t *resolver) { REQUIRE(VALID_RESOLVER(resolver)); return (dns_dispatchset_get(resolver->dispatches6)); } isc_socketmgr_t * dns_resolver_socketmgr(dns_resolver_t *resolver) { REQUIRE(VALID_RESOLVER(resolver)); return (resolver->socketmgr); } isc_taskmgr_t * dns_resolver_taskmgr(dns_resolver_t *resolver) { REQUIRE(VALID_RESOLVER(resolver)); return (resolver->taskmgr); } isc_uint32_t dns_resolver_getlamettl(dns_resolver_t *resolver) { REQUIRE(VALID_RESOLVER(resolver)); return (resolver->lame_ttl); } void dns_resolver_setlamettl(dns_resolver_t *resolver, isc_uint32_t lame_ttl) { REQUIRE(VALID_RESOLVER(resolver)); resolver->lame_ttl = lame_ttl; } unsigned int dns_resolver_nrunning(dns_resolver_t *resolver) { unsigned int n; LOCK(&resolver->nlock); n = resolver->nfctx; UNLOCK(&resolver->nlock); return (n); } isc_result_t dns_resolver_addalternate(dns_resolver_t *resolver, isc_sockaddr_t *alt, dns_name_t *name, in_port_t port) { alternate_t *a; isc_result_t result; REQUIRE(VALID_RESOLVER(resolver)); REQUIRE(!resolver->frozen); REQUIRE((alt == NULL) ^ (name == NULL)); a = isc_mem_get(resolver->mctx, sizeof(*a)); if (a == NULL) return (ISC_R_NOMEMORY); if (alt != NULL) { a->isaddress = ISC_TRUE; a->_u.addr = *alt; } else { a->isaddress = ISC_FALSE; a->_u._n.port = port; dns_name_init(&a->_u._n.name, NULL); result = dns_name_dup(name, resolver->mctx, &a->_u._n.name); if (result != ISC_R_SUCCESS) { isc_mem_put(resolver->mctx, a, sizeof(*a)); return (result); } } ISC_LINK_INIT(a, link); ISC_LIST_APPEND(resolver->alternates, a, link); return (ISC_R_SUCCESS); } void dns_resolver_setudpsize(dns_resolver_t *resolver, isc_uint16_t udpsize) { REQUIRE(VALID_RESOLVER(resolver)); resolver->udpsize = udpsize; } isc_uint16_t dns_resolver_getudpsize(dns_resolver_t *resolver) { REQUIRE(VALID_RESOLVER(resolver)); return (resolver->udpsize); } void dns_resolver_flushbadcache(dns_resolver_t *resolver, dns_name_t *name) { unsigned int i; dns_badcache_t *bad, *prev, *next; REQUIRE(VALID_RESOLVER(resolver)); LOCK(&resolver->lock); if (resolver->badcache == NULL) goto unlock; if (name != NULL) { isc_time_t now; isc_result_t result; result = isc_time_now(&now); if (result != ISC_R_SUCCESS) isc_time_settoepoch(&now); i = dns_name_hash(name, ISC_FALSE) % resolver->badhash; prev = NULL; for (bad = resolver->badcache[i]; bad != NULL; bad = next) { int n; next = bad->next; n = isc_time_compare(&bad->expire, &now); if (n < 0 || dns_name_equal(name, &bad->name)) { if (prev == NULL) resolver->badcache[i] = bad->next; else prev->next = bad->next; isc_mem_put(resolver->mctx, bad, sizeof(*bad) + bad->name.length); resolver->badcount--; } else prev = bad; } } else destroy_badcache(resolver); unlock: UNLOCK(&resolver->lock); } static void resizehash(dns_resolver_t *resolver, isc_time_t *now, isc_boolean_t grow) { unsigned int newsize; dns_badcache_t **new, *bad, *next; unsigned int i; if (grow) newsize = resolver->badhash * 2 + 1; else newsize = (resolver->badhash - 1) / 2; new = isc_mem_get(resolver->mctx, sizeof(*resolver->badcache) * newsize); if (new == NULL) return; memset(new, 0, sizeof(*resolver->badcache) * newsize); for (i = 0; i < resolver->badhash; i++) { for (bad = resolver->badcache[i]; bad != NULL; bad = next) { next = bad->next; if (isc_time_compare(&bad->expire, now) < 0) { isc_mem_put(resolver->mctx, bad, sizeof(*bad) + bad->name.length); resolver->badcount--; } else { bad->next = new[bad->hashval % newsize]; new[bad->hashval % newsize] = bad; } } } isc_mem_put(resolver->mctx, resolver->badcache, sizeof(*resolver->badcache) * resolver->badhash); resolver->badhash = newsize; resolver->badcache = new; } void dns_resolver_addbadcache(dns_resolver_t *resolver, dns_name_t *name, dns_rdatatype_t type, isc_time_t *expire) { isc_time_t now; isc_result_t result = ISC_R_SUCCESS; unsigned int i, hashval; dns_badcache_t *bad, *prev, *next; REQUIRE(VALID_RESOLVER(resolver)); LOCK(&resolver->lock); if (resolver->badcache == NULL) { resolver->badcache = isc_mem_get(resolver->mctx, sizeof(*resolver->badcache) * DNS_BADCACHE_SIZE); if (resolver->badcache == NULL) goto cleanup; resolver->badhash = DNS_BADCACHE_SIZE; memset(resolver->badcache, 0, sizeof(*resolver->badcache) * resolver->badhash); } result = isc_time_now(&now); if (result != ISC_R_SUCCESS) isc_time_settoepoch(&now); hashval = dns_name_hash(name, ISC_FALSE); i = hashval % resolver->badhash; prev = NULL; for (bad = resolver->badcache[i]; bad != NULL; bad = next) { next = bad->next; if (bad->type == type && dns_name_equal(name, &bad->name)) break; if (isc_time_compare(&bad->expire, &now) < 0) { if (prev == NULL) resolver->badcache[i] = bad->next; else prev->next = bad->next; isc_mem_put(resolver->mctx, bad, sizeof(*bad) + bad->name.length); resolver->badcount--; } else prev = bad; } if (bad == NULL) { isc_buffer_t buffer; bad = isc_mem_get(resolver->mctx, sizeof(*bad) + name->length); if (bad == NULL) goto cleanup; bad->type = type; bad->hashval = hashval; bad->expire = *expire; isc_buffer_init(&buffer, bad + 1, name->length); dns_name_init(&bad->name, NULL); dns_name_copy(name, &bad->name, &buffer); bad->next = resolver->badcache[i]; resolver->badcache[i] = bad; resolver->badcount++; if (resolver->badcount > resolver->badhash * 8) resizehash(resolver, &now, ISC_TRUE); if (resolver->badcount < resolver->badhash * 2 && resolver->badhash > DNS_BADCACHE_SIZE) resizehash(resolver, &now, ISC_FALSE); } else bad->expire = *expire; cleanup: UNLOCK(&resolver->lock); } isc_boolean_t dns_resolver_getbadcache(dns_resolver_t *resolver, dns_name_t *name, dns_rdatatype_t type, isc_time_t *now) { dns_badcache_t *bad, *prev, *next; isc_boolean_t answer = ISC_FALSE; unsigned int i; REQUIRE(VALID_RESOLVER(resolver)); LOCK(&resolver->lock); if (resolver->badcache == NULL) goto unlock; i = dns_name_hash(name, ISC_FALSE) % resolver->badhash; prev = NULL; for (bad = resolver->badcache[i]; bad != NULL; bad = next) { next = bad->next; /* * Search the hash list. Clean out expired records as we go. */ if (isc_time_compare(&bad->expire, now) < 0) { if (prev != NULL) prev->next = bad->next; else resolver->badcache[i] = bad->next; isc_mem_put(resolver->mctx, bad, sizeof(*bad) + bad->name.length); resolver->badcount--; continue; } if (bad->type == type && dns_name_equal(name, &bad->name)) { answer = ISC_TRUE; break; } prev = bad; } /* * Slow sweep to clean out stale records. */ i = resolver->badsweep++ % resolver->badhash; bad = resolver->badcache[i]; if (bad != NULL && isc_time_compare(&bad->expire, now) < 0) { resolver->badcache[i] = bad->next; isc_mem_put(resolver->mctx, bad, sizeof(*bad) + bad->name.length); resolver->badcount--; } unlock: UNLOCK(&resolver->lock); return (answer); } void dns_resolver_printbadcache(dns_resolver_t *resolver, FILE *fp) { char namebuf[DNS_NAME_FORMATSIZE]; char typebuf[DNS_RDATATYPE_FORMATSIZE]; dns_badcache_t *bad, *next, *prev; isc_time_t now; unsigned int i; isc_uint64_t t; LOCK(&resolver->lock); fprintf(fp, ";\n; Bad cache\n;\n"); if (resolver->badcache == NULL) goto unlock; TIME_NOW(&now); for (i = 0; i < resolver->badhash; i++) { prev = NULL; for (bad = resolver->badcache[i]; bad != NULL; bad = next) { next = bad->next; if (isc_time_compare(&bad->expire, &now) < 0) { if (prev != NULL) prev->next = bad->next; else resolver->badcache[i] = bad->next; isc_mem_put(resolver->mctx, bad, sizeof(*bad) + bad->name.length); resolver->badcount--; continue; } prev = bad; dns_name_format(&bad->name, namebuf, sizeof(namebuf)); dns_rdatatype_format(bad->type, typebuf, sizeof(typebuf)); t = isc_time_microdiff(&bad->expire, &now); t /= 1000; fprintf(fp, "; %s/%s [ttl " "%" ISC_PLATFORM_QUADFORMAT "u]\n", namebuf, typebuf, t); } } unlock: UNLOCK(&resolver->lock); } static void free_algorithm(void *node, void *arg) { unsigned char *algorithms = node; isc_mem_t *mctx = arg; isc_mem_put(mctx, algorithms, *algorithms); } void dns_resolver_reset_algorithms(dns_resolver_t *resolver) { REQUIRE(VALID_RESOLVER(resolver)); #if USE_ALGLOCK RWLOCK(&resolver->alglock, isc_rwlocktype_write); #endif if (resolver->algorithms != NULL) dns_rbt_destroy(&resolver->algorithms); #if USE_ALGLOCK RWUNLOCK(&resolver->alglock, isc_rwlocktype_write); #endif } isc_result_t dns_resolver_disable_algorithm(dns_resolver_t *resolver, dns_name_t *name, unsigned int alg) { unsigned int len, mask; unsigned char *new; unsigned char *algorithms; isc_result_t result; dns_rbtnode_t *node = NULL; REQUIRE(VALID_RESOLVER(resolver)); if (alg > 255) return (ISC_R_RANGE); #if USE_ALGLOCK RWLOCK(&resolver->alglock, isc_rwlocktype_write); #endif if (resolver->algorithms == NULL) { result = dns_rbt_create(resolver->mctx, free_algorithm, resolver->mctx, &resolver->algorithms); if (result != ISC_R_SUCCESS) goto cleanup; } len = alg/8 + 2; mask = 1 << (alg%8); result = dns_rbt_addnode(resolver->algorithms, name, &node); if (result == ISC_R_SUCCESS || result == ISC_R_EXISTS) { algorithms = node->data; if (algorithms == NULL || len > *algorithms) { new = isc_mem_get(resolver->mctx, len); if (new == NULL) { result = ISC_R_NOMEMORY; goto cleanup; } memset(new, 0, len); if (algorithms != NULL) memmove(new, algorithms, *algorithms); new[len-1] |= mask; *new = len; node->data = new; if (algorithms != NULL) isc_mem_put(resolver->mctx, algorithms, *algorithms); } else algorithms[len-1] |= mask; } result = ISC_R_SUCCESS; cleanup: #if USE_ALGLOCK RWUNLOCK(&resolver->alglock, isc_rwlocktype_write); #endif return (result); } isc_boolean_t dns_resolver_algorithm_supported(dns_resolver_t *resolver, dns_name_t *name, unsigned int alg) { unsigned int len, mask; unsigned char *algorithms; void *data = NULL; isc_result_t result; isc_boolean_t found = ISC_FALSE; REQUIRE(VALID_RESOLVER(resolver)); + /* + * DH is unsupported for DNSKEYs, see RFC 4034 sec. A.1. + */ + if ((alg == DST_ALG_DH) || (alg == DST_ALG_INDIRECT)) + return (ISC_FALSE); + #if USE_ALGLOCK RWLOCK(&resolver->alglock, isc_rwlocktype_read); #endif if (resolver->algorithms == NULL) goto unlock; result = dns_rbt_findname(resolver->algorithms, name, 0, NULL, &data); if (result == ISC_R_SUCCESS || result == DNS_R_PARTIALMATCH) { len = alg/8 + 2; mask = 1 << (alg%8); algorithms = data; if (len <= *algorithms && (algorithms[len-1] & mask) != 0) found = ISC_TRUE; } unlock: #if USE_ALGLOCK RWUNLOCK(&resolver->alglock, isc_rwlocktype_read); #endif if (found) return (ISC_FALSE); + return (dst_algorithm_supported(alg)); } isc_boolean_t dns_resolver_digest_supported(dns_resolver_t *resolver, unsigned int digest) { UNUSED(resolver); return (dns_ds_digest_supported(digest)); } void dns_resolver_resetmustbesecure(dns_resolver_t *resolver) { REQUIRE(VALID_RESOLVER(resolver)); #if USE_MBSLOCK RWLOCK(&resolver->mbslock, isc_rwlocktype_write); #endif if (resolver->mustbesecure != NULL) dns_rbt_destroy(&resolver->mustbesecure); #if USE_MBSLOCK RWUNLOCK(&resolver->mbslock, isc_rwlocktype_write); #endif } static isc_boolean_t yes = ISC_TRUE, no = ISC_FALSE; isc_result_t dns_resolver_setmustbesecure(dns_resolver_t *resolver, dns_name_t *name, isc_boolean_t value) { isc_result_t result; REQUIRE(VALID_RESOLVER(resolver)); #if USE_MBSLOCK RWLOCK(&resolver->mbslock, isc_rwlocktype_write); #endif if (resolver->mustbesecure == NULL) { result = dns_rbt_create(resolver->mctx, NULL, NULL, &resolver->mustbesecure); if (result != ISC_R_SUCCESS) goto cleanup; } result = dns_rbt_addname(resolver->mustbesecure, name, value ? &yes : &no); cleanup: #if USE_MBSLOCK RWUNLOCK(&resolver->mbslock, isc_rwlocktype_write); #endif return (result); } isc_boolean_t dns_resolver_getmustbesecure(dns_resolver_t *resolver, dns_name_t *name) { void *data = NULL; isc_boolean_t value = ISC_FALSE; isc_result_t result; REQUIRE(VALID_RESOLVER(resolver)); #if USE_MBSLOCK RWLOCK(&resolver->mbslock, isc_rwlocktype_read); #endif if (resolver->mustbesecure == NULL) goto unlock; result = dns_rbt_findname(resolver->mustbesecure, name, 0, NULL, &data); if (result == ISC_R_SUCCESS || result == DNS_R_PARTIALMATCH) value = *(isc_boolean_t*)data; unlock: #if USE_MBSLOCK RWUNLOCK(&resolver->mbslock, isc_rwlocktype_read); #endif return (value); } void dns_resolver_getclientsperquery(dns_resolver_t *resolver, isc_uint32_t *cur, isc_uint32_t *min, isc_uint32_t *max) { REQUIRE(VALID_RESOLVER(resolver)); LOCK(&resolver->lock); if (cur != NULL) *cur = resolver->spillat; if (min != NULL) *min = resolver->spillatmin; if (max != NULL) *max = resolver->spillatmax; UNLOCK(&resolver->lock); } void dns_resolver_setclientsperquery(dns_resolver_t *resolver, isc_uint32_t min, isc_uint32_t max) { REQUIRE(VALID_RESOLVER(resolver)); LOCK(&resolver->lock); resolver->spillatmin = resolver->spillat = min; resolver->spillatmax = max; UNLOCK(&resolver->lock); } isc_boolean_t dns_resolver_getzeronosoattl(dns_resolver_t *resolver) { REQUIRE(VALID_RESOLVER(resolver)); return (resolver->zero_no_soa_ttl); } void dns_resolver_setzeronosoattl(dns_resolver_t *resolver, isc_boolean_t state) { REQUIRE(VALID_RESOLVER(resolver)); resolver->zero_no_soa_ttl = state; } unsigned int dns_resolver_getoptions(dns_resolver_t *resolver) { REQUIRE(VALID_RESOLVER(resolver)); return (resolver->options); } unsigned int dns_resolver_gettimeout(dns_resolver_t *resolver) { REQUIRE(VALID_RESOLVER(resolver)); return (resolver->query_timeout); } void dns_resolver_settimeout(dns_resolver_t *resolver, unsigned int seconds) { REQUIRE(VALID_RESOLVER(resolver)); if (seconds == 0) seconds = DEFAULT_QUERY_TIMEOUT; if (seconds > MAXIMUM_QUERY_TIMEOUT) seconds = MAXIMUM_QUERY_TIMEOUT; if (seconds < MINIMUM_QUERY_TIMEOUT) seconds = MINIMUM_QUERY_TIMEOUT; resolver->query_timeout = seconds; } void dns_resolver_setmaxdepth(dns_resolver_t *resolver, unsigned int maxdepth) { REQUIRE(VALID_RESOLVER(resolver)); resolver->maxdepth = maxdepth; } unsigned int dns_resolver_getmaxdepth(dns_resolver_t *resolver) { REQUIRE(VALID_RESOLVER(resolver)); return (resolver->maxdepth); } void dns_resolver_setmaxqueries(dns_resolver_t *resolver, unsigned int queries) { REQUIRE(VALID_RESOLVER(resolver)); resolver->maxqueries = queries; } unsigned int dns_resolver_getmaxqueries(dns_resolver_t *resolver) { REQUIRE(VALID_RESOLVER(resolver)); return (resolver->maxqueries); }