diff --git a/services/authzone.c b/services/authzone.c index 72b37fef9..b3fb09e64 100644 --- a/services/authzone.c +++ b/services/authzone.c @@ -392,6 +392,20 @@ auth_data_del(rbnode_type* n, void* ATTR_UNUSED(arg)) auth_data_delete(z); } +/** delete chunklist */ +static void +auth_chunk_list_delete(struct auth_chunk* first) +{ + struct auth_chunk* c, *cn; + c = first; + while(c) { + cn = c->next; + free(c->data); + free(c); + c = cn; + } +} + /** delete an auth zone structure (tree remove must be done elsewhere) */ static void auth_zone_delete(struct auth_zone* z, struct auth_zones* az) @@ -413,6 +427,7 @@ auth_zone_delete(struct auth_zone* z, struct auth_zones* az) } if(z->rpz) rpz_delete(z->rpz); + auth_chunk_list_delete(z->perform_write_chunk_list); free(z->name); free(z->zonefile); free(z); @@ -2369,14 +2384,7 @@ static void auth_chunks_delete(struct auth_transfer* at) { if(at->chunks_first) { - struct auth_chunk* c, *cn; - c = at->chunks_first; - while(c) { - cn = c->next; - free(c->data); - free(c); - c = cn; - } + auth_chunk_list_delete(at->chunks_first); } at->chunks_first = NULL; at->chunks_last = NULL; @@ -3593,7 +3601,13 @@ int auth_zones_lookup(struct auth_zones* az, struct query_info* qinfo, *fallback = 1; return 0; } - if(z->zone_expired) { + if(z->zone_expired || (z->zonemd_check && z->zonemd_callback_env)) { + /* Do not serve from a zonemd-check zone while its ZONEMD + * verification is still pending: the content is not yet known + * to pass the configured check. The pending marker + * (zonemd_callback_env) is set under z->lock when the async + * lookup is spawned and cleared by the callback under z->lock, + * so this test is race-free. */ *fallback = z->fallback_enabled; lock_rw_unlock(&z->lock); return 0; @@ -3695,7 +3709,10 @@ int auth_zones_downstream_answer(struct auth_zones* az, struct module_env* env, lock_rw_unlock(&z->lock); return 0; } - if(z->zone_expired) { + if(z->zone_expired || (z->zonemd_check && z->zonemd_callback_env)) { + /* see auth_zones_lookup: a pending ZONEMD verification is + * treated like expiry - the zone content is not yet known + * to pass the configured check. */ if(z->fallback_enabled) { lock_rw_unlock(&z->lock); return 0; @@ -5299,7 +5316,7 @@ apply_http(struct auth_xfer* xfr, struct auth_zone* z, /** write http chunks to zonefile to create downloaded file */ static int -auth_zone_write_chunks(struct auth_xfer* xfr, const char* fname) +auth_zone_write_chunks(struct auth_chunk* chunk_list, const char* fname) { FILE* out; struct auth_chunk* p; @@ -5308,7 +5325,7 @@ auth_zone_write_chunks(struct auth_xfer* xfr, const char* fname) log_err("could not open %s: %s", fname, strerror(errno)); return 0; } - for(p = xfr->task_transfer->chunks_first; p ; p = p->next) { + for(p = chunk_list; p ; p = p->next) { if(!write_out(out, (char*)p->data, p->len)) { log_err("could not write http download to %s", fname); fclose(out); @@ -5319,34 +5336,18 @@ auth_zone_write_chunks(struct auth_xfer* xfr, const char* fname) return 1; } -/** write to zonefile after zone has been updated */ +/** write to zonefile after zone has been updated, z has rdlock by caller. */ static void -xfr_write_after_update(struct auth_xfer* xfr, struct module_env* env) +zone_write_after_update(struct auth_zone* z, struct module_env* env, + struct auth_chunk* chunk_list) { struct config_file* cfg = env->cfg; - struct auth_zone* z; char tmpfile[1024]; char* zfilename; - lock_basic_unlock(&xfr->lock); - - /* get lock again, so it is a readlock and concurrently queries - * can be answered */ - lock_rw_rdlock(&env->auth_zones->lock); - z = auth_zone_find(env->auth_zones, xfr->name, xfr->namelen, - xfr->dclass); - if(!z) { - lock_rw_unlock(&env->auth_zones->lock); - /* the zone is gone, ignore xfr results */ - lock_basic_lock(&xfr->lock); - return; - } - lock_rw_rdlock(&z->lock); - lock_basic_lock(&xfr->lock); - lock_rw_unlock(&env->auth_zones->lock); if(z->zonefile == NULL || z->zonefile[0] == 0) { - lock_rw_unlock(&z->lock); /* no write needed, no zonefile set */ + auth_chunk_list_delete(chunk_list); return; } zfilename = z->zonefile; @@ -5363,21 +5364,21 @@ xfr_write_after_update(struct auth_xfer* xfr, struct module_env* env) if((size_t)strlen(zfilename) + 16 > sizeof(tmpfile)) { verbose(VERB_ALGO, "tmpfilename too long, cannot update " " zonefile %s", zfilename); - lock_rw_unlock(&z->lock); + auth_chunk_list_delete(chunk_list); return; } snprintf(tmpfile, sizeof(tmpfile), "%s.tmp%u", zfilename, (unsigned)getpid()); - if(xfr->task_transfer->master->http) { + if(chunk_list) { /* use the stored chunk list to write them */ - if(!auth_zone_write_chunks(xfr, tmpfile)) { + if(!auth_zone_write_chunks(chunk_list, tmpfile)) { unlink(tmpfile); - lock_rw_unlock(&z->lock); + auth_chunk_list_delete(chunk_list); return; } + auth_chunk_list_delete(chunk_list); } else if(!auth_zone_write_file(z, tmpfile)) { unlink(tmpfile); - lock_rw_unlock(&z->lock); return; } #ifdef UB_ON_WINDOWS @@ -5387,9 +5388,57 @@ xfr_write_after_update(struct auth_xfer* xfr, struct module_env* env) log_err("could not rename(%s, %s): %s", tmpfile, zfilename, strerror(errno)); unlink(tmpfile); - lock_rw_unlock(&z->lock); return; } +} + +/** write to zonefile after zone has updated, reacquires z readlock. */ +static void +zone_write_after_update_reacq(uint8_t* bakname, size_t baknamelen, + uint16_t bakdclass, struct module_env* env, + struct auth_chunk* chunk_list) +{ + struct auth_zone* z; + /* get lock again, so it is a readlock and concurrently queries + * can be answered */ + lock_rw_rdlock(&env->auth_zones->lock); + z = auth_zone_find(env->auth_zones, bakname, baknamelen, bakdclass); + if(!z) { + lock_rw_unlock(&env->auth_zones->lock); + /* the zone is gone, ignore xfr results */ + return; + } + lock_rw_rdlock(&z->lock); + lock_rw_unlock(&env->auth_zones->lock); + + zone_write_after_update(z, env, chunk_list); + lock_rw_unlock(&z->lock); +} + +/** write to zonefile after zone has been updated */ +static void +xfr_write_after_update(struct auth_xfer* xfr, struct module_env* env, + struct auth_chunk* chunk_list) +{ + struct auth_zone* z; + lock_basic_unlock(&xfr->lock); + + /* get lock again, so it is a readlock and concurrently queries + * can be answered */ + lock_rw_rdlock(&env->auth_zones->lock); + z = auth_zone_find(env->auth_zones, xfr->name, xfr->namelen, + xfr->dclass); + if(!z) { + lock_rw_unlock(&env->auth_zones->lock); + /* the zone is gone, ignore xfr results */ + lock_basic_lock(&xfr->lock); + return; + } + lock_rw_rdlock(&z->lock); + lock_basic_lock(&xfr->lock); + lock_rw_unlock(&env->auth_zones->lock); + + zone_write_after_update(z, env, chunk_list); lock_rw_unlock(&z->lock); } @@ -5422,6 +5471,8 @@ xfr_process_chunk_list(struct auth_xfer* xfr, struct module_env* env, int* ixfr_fail) { struct auth_zone* z; + int zonemd_in_progress; + struct auth_chunk* current_chunk_list = NULL; /* obtain locks and structures */ lock_basic_unlock(&xfr->lock); @@ -5505,6 +5556,25 @@ xfr_process_chunk_list(struct auth_xfer* xfr, struct module_env* env, if(z->rpz) rpz_finish_config(z->rpz); + if(z->zonemd_check && z->zonemd_callback_env) { + zonemd_in_progress = 1; + z->zonemd_callback_perform_write = 1; + auth_chunk_list_delete(z->perform_write_chunk_list); + z->perform_write_chunk_list = NULL; + if(xfr->task_transfer->master->http) { + z->perform_write_chunk_list = xfr->task_transfer->chunks_first; + xfr->task_transfer->chunks_first = NULL; + auth_chunks_delete(xfr->task_transfer); + } + } else { + zonemd_in_progress = 0; + z->zonemd_callback_perform_write = 0; + if(xfr->task_transfer->master->http) { + current_chunk_list = xfr->task_transfer->chunks_first; + xfr->task_transfer->chunks_first = NULL; + auth_chunks_delete(xfr->task_transfer); + } + } /* unlock */ lock_rw_unlock(&z->lock); @@ -5515,7 +5585,9 @@ xfr_process_chunk_list(struct auth_xfer* xfr, struct module_env* env, (unsigned)xfr->serial); } /* see if we need to write to a zonefile */ - xfr_write_after_update(xfr, env); + if(!zonemd_in_progress) { + xfr_write_after_update(xfr, env, current_chunk_list); + } return 1; } @@ -8601,9 +8673,13 @@ void auth_zonemd_dnskey_lookup_callback(void* arg, int rcode, sldns_buffer* buf, char reasonbuf[256]; char* reason = NULL, *ds_bogus = NULL, *typestr="DNSKEY"; struct ub_packed_rrset_key* dnskey = NULL, *ds = NULL; - int is_insecure = 0, downprot; + int is_insecure = 0, downprot, perform_write = 0; struct ub_packed_rrset_key keystorage; uint8_t sigalg[ALGO_NEEDS_MAX+1]; + uint8_t bakname[LDNS_MAX_DOMAINLEN]; + size_t baknamelen; + uint16_t bakdclass; + struct auth_chunk* chunk_list = NULL; lock_rw_wrlock(&z->lock); env = z->zonemd_callback_env; @@ -8726,7 +8802,37 @@ void auth_zonemd_dnskey_lookup_callback(void* arg, int rcode, sldns_buffer* buf, auth_zone_verify_zonemd_with_key(z, env, &env->mesh->mods, dnskey, is_insecure, NULL, downprot?sigalg:NULL); regional_free_all(env->scratch); + + if(z->zonemd_callback_perform_write) { + if(!z->zone_expired) { + /* Write to zonefile if the ZONEMD is okay. */ + perform_write = 1; + /* copy the key to lookup the z structure. + * The new lookup is readonly so concurrent + * queries can continue. */ + if(z->namelen > sizeof(bakname)) { + perform_write = 0; + auth_chunk_list_delete(z->perform_write_chunk_list); + z->perform_write_chunk_list = NULL; + } else { + memcpy(bakname, z->name, z->namelen); + baknamelen = z->namelen; + bakdclass = z->dclass; + chunk_list = z->perform_write_chunk_list; + z->perform_write_chunk_list = NULL; + } + } else { + auth_chunk_list_delete(z->perform_write_chunk_list); + z->perform_write_chunk_list = NULL; + } + z->zonemd_callback_perform_write = 0; + } lock_rw_unlock(&z->lock); + + if(perform_write) { + zone_write_after_update_reacq(bakname, baknamelen, bakdclass, + env, chunk_list); + } } /** lookup DNSKEY for ZONEMD verification */ @@ -8794,6 +8900,9 @@ zonemd_lookup_dnskey(struct auth_zone* z, struct module_env* env) &auth_zonemd_dnskey_lookup_callback, z, 0, &z->zonemd_callback_unique_info)) { lock_rw_wrlock(&z->lock); + /* no callback will run; do not leave the pending + * marker set */ + z->zonemd_callback_env = NULL; log_err("out of memory lookup of %s for zonemd", (fetch_ds?"DS":"DNSKEY")); return 0; diff --git a/services/authzone.h b/services/authzone.h index 97ab01fcb..24c8c80b5 100644 --- a/services/authzone.h +++ b/services/authzone.h @@ -146,6 +146,10 @@ struct auth_zone { uint16_t zonemd_callback_qtype; /** for the zonemd callback, the unique info */ void* zonemd_callback_unique_info; + /** if the zonemd callback should write to file */ + int zonemd_callback_perform_write; + /** chunklist to write for chunked transfer. */ + struct auth_chunk* perform_write_chunk_list; /** zone has been deleted */ int zone_deleted; /** deletelist pointer, unused normally except during delete */ diff --git a/testdata/auth_zonemd_xfr_chain_fail.rpl b/testdata/auth_zonemd_xfr_chain_fail.rpl index 3e09c9e8e..76a989f75 100644 --- a/testdata/auth_zonemd_xfr_chain_fail.rpl +++ b/testdata/auth_zonemd_xfr_chain_fail.rpl @@ -283,39 +283,9 @@ www.example.com. IN A SECTION ANSWER ENTRY_END -; the zonefile was updated with new contents +; the zonefile was not updated with new contents, due to zonemd failure STEP 70 CHECK_TEMPFILE example.com FILE_BEGIN -example.com. 3600 IN SOA ns.example.com. hostmaster.example.com. 200154054 28800 7200 604800 3600 -example.com. 3600 IN RRSIG SOA 8 2 3600 20201116135527 20201019135527 55566 example.com. gcFHT/Q4iDZ78CK6fyY2HZr8sRtgH2Rna9fEs06RW0gqMnfDntweoIaBamOZ7NlAP84aY2bZeanmEccmkHexByUpodCoKQ4NzVXctLr0TO4PVoFyfUfj62fjhM56SF8ioDxsoDQcPtYXcjNQjwfntWofMqHCMxrb9LzbgePzhOM= -example.com. 3600 IN NS ns.example.com. -example.com. 3600 IN RRSIG NS 8 2 3600 20201116135527 20201019135527 55566 example.com. X+V3XsbJbBi9OsHpjMkGCox8RLY/uXp/XX/O/flTrIre9fMDWm9ZGnewtuQFpLgGc6hUTi0eLsuRWRA5fZXEKUBhmoR2Ph01KgE1gvlL7v6zPWQwXVcBRUr3mOSbYdNNkHkXEjiDBGEhNkfqR216zNgw563eEGXOkLUFNIx5Zpg= -example.com. 3600 IN NSEC bar.example.com. NS SOA RRSIG NSEC DNSKEY ZONEMD -example.com. 3600 IN RRSIG NSEC 8 2 3600 20201116135527 20201019135527 55566 example.com. ufLrlOQprAqjnH85Rt3T0Mxd3ZB0mBeeNIr84eFJ8Rk6WiWEPm0Y1R7GRufNI24Mj7iqLcL4nJM6KK6B7dJqjqu73jw1acuYNnbsoV2BNDRXRFP2FNWTpctVdi+955f3FzgsmEJXfGiSUG0YXAEcZmdCPCn5ii2jk8mk7r6KKYo= -example.com. 3600 IN DNSKEY 256 3 8 AwEAAdug/L739i0mgN2nuK/bhxu3wFn5Ud9nK2+XUmZQlPUEZUC5YZvm1rfMmEWTGBn87fFxEu/kjFZHJ55JLzqsbbpVHLbmKCTT2gYR2FV2WDKROGKuYbVkJIXdKAjJ0ONuK507NinYvlWXIoxHn22KAWOd9wKgSTNHBlmGkX+ts3hh ;{id = 55566} -example.com. 3600 IN RRSIG DNSKEY 8 2 3600 20201116135527 20201019135527 55566 example.com. fsdnVg38PKQTH2mDOwkXL6Jre7JP7Gf8WI3CvIbmeYQUJtAlpcSbZkS3wInm3kKMxOuT55BWzndQzpfmpo91OqJjG27W0k9301NMLUwFprA6b9HK+iPAT0JpYPDPzcm1bQdarLzLS+eD/GPwmyVSX7Gze+08VfE8m8sOW2r7UjA= -example.com. 3600 IN ZONEMD 200154054 1 2 58F7620F93204BBB31B44F795B3409CC4ABD9EF5601DECC15675BD7751213152984EDDCE0626E6062E744B03B3E47711202FBB79E4A2EB8BC5CF46741B5CAE6F -example.com. 3600 IN RRSIG ZONEMD 8 2 3600 20201116135527 20201019135527 55566 example.com. orn8ZF/yqj9u4WrhiO6gtEcTaVsnZSWWZLfXhcIOiWSB8kKCxtZl5cG17dD3Du1NllUwMRqkp0KleLhIoUS9xeQ/0x05u+CYLrfQ62oAiD7q54ZQzpXJIH52aQzKV70ZnO03CZowhQBnetmIoKX6xLogKo8pt+BdQbo3oVHxV8Y= -bar.example.com. 3600 IN A 1.2.3.55 -bar.example.com. 3600 IN RRSIG A 8 3 3600 20201116135527 20201019135527 55566 example.com. NYhmRicF4C9+YxpWeQrepy4ALM1CM0USoDuGi3W5Xtp4/+YpCJfSIdR9vlJaJ2WayYuZrz9Ai2ci7oWwE1Fn3oywGwCKvGo9m0c3mC2eEtphE19wrop6pWu6um4RiFhmzYS1voraA3PAdYzze9U4NHzlk0+sb5vNZW9dSZS30Ds= -bar.example.com. 3600 IN NSEC ding.example.com. A RRSIG NSEC -bar.example.com. 3600 IN RRSIG NSEC 8 3 3600 20201116135527 20201019135527 55566 example.com. VhsGuBx20DXQZNU8ITAMnasn6NVyEjN9xtB8msH5xJn80UCuaqvFBURzcPWN3aHnykEvGfdPF/9P3WvlON0cMikWkqSLy6Q9bpvgAq13HWYh+ZcDoqLtICaB7RkBQc+6aHAqZFyQbD8/m8Kxt5eVJtV6rEuf+yPX0+3aXHhsRg0= -ding.example.com. 3600 IN A 1.2.3.4 -ding.example.com. 3600 IN RRSIG A 8 3 3600 20201116135527 20201019135527 55566 example.com. OERsruISkpd1s68ute8Xm8YXisBCTkkiDMt34K+0dVqvySOJq63d3qN18BeUxZxLyHDB1eR3nZZKqEdkTqrv2r98skhWhjnOECpFbu5gKjtN/KPexbbJ+rxC0QqciuWOC7M6YE0cvI17/RB9KhVRy5rqY2X4Gt2wk2CNeD1dAko= -ding.example.com. 3600 IN NSEC foo.example.com. A RRSIG NSEC -ding.example.com. 3600 IN RRSIG NSEC 8 3 3600 20201116135527 20201019135527 55566 example.com. nb1W2aaKrU5iAQiY8gMsoMOejID19JMTEwY2rRoe+KsvzMs0rE0ifEkqit4blXaU0tfy0foJ70uqdJFqBoGz1NcSwZ6GNk/iNfGvG3XpxZ/zqEe7kkIucqqei794G7z9psqV94yZ3WaT+IswPpWrSaWv1w41RtcWufPhe4fOAmU= -foo.example.com. 3600 IN A 1.2.3.4 -foo.example.com. 3600 IN RRSIG A 8 3 3600 20201116135527 20201019135527 55566 example.com. ZcUngb2pUejwnsshbJN/Dfr+Bzu8fcZXyqLArQ+10Bw1IPHyfx7yyUJ43V5tTYVHPSEsJzTnaWj+olVrNhVZxq5e0pgzSYPfGln2FEItEvMIOn33j8yKTpPW2MLyuFF5ZkXhosG20EUwRMvMmRHRz9mIZfwWoMbSGPukmLh8zMA= -foo.example.com. 3600 IN NSEC ns.example.com. A RRSIG NSEC -foo.example.com. 3600 IN RRSIG NSEC 8 3 3600 20201116135527 20201019135527 55566 example.com. fUZEpkEULRWDntN5Z7Kr8M83Hjhf08ECMKRpo6IBoBc3ayenj+YMgWAvFXC825wjENPYYWNGag0d32U83zCZxqgv+8uXZd3B7QDpTbL41aWZdc++s5YWTkYjyOWwJ1XHOv4nL3qEnJBXVzo/E1gbSKhTFuG97i+7J1MFd9MsC5s= -ns.example.com. 3600 IN A 127.0.0.1 -ns.example.com. 3600 IN RRSIG A 8 3 3600 20201116135527 20201019135527 55566 example.com. SiuxuPtN/ITd+Z20j8UNUHJWbLHirE8zQOWMv5fAZ1rPKpAidrZgUL8J417GdrTwkueU2ywAJ7EzFJSwNTa7o/wUnq7svmOR6Ze6UQsKuZFZGEfqPNDRp4YuF86LU5jChuo+f/IRpydHrxVwGxDPCR9KarDM+ewfW+yI5bZeZcg= -ns.example.com. 3600 IN NSEC www.example.com. A RRSIG NSEC -ns.example.com. 3600 IN RRSIG NSEC 8 3 3600 20201116135527 20201019135527 55566 example.com. 0upKNYjiow4NDJm3I1RbUddE9GGuFYEVKswww5BAc/6WHuukupncL30lskvcSKGpByDssP2Hi2CufyEtYeGWh6q1TxtOFRqFBX1p6Q5b3tBlCtvv4h31dQR9uqLvq+GkGS5MR+0LO5kWagIpZmnI8YY5plVdXEtNbp2Ar8zvz/A= -www.example.com. 3600 IN A 127.0.0.1 -www.example.com. 3600 IN RRSIG A 8 3 3600 20201116135527 20201019135527 55566 example.com. AaIeICaPjV50TDrpbyOn94+hs8EYIMTmN4pYqj7e8GIGimqQIk5jgpwSx6SOoOF+uOqkf9GKHkQTn5YVGaeXwEQleg7mPTmMYKAOk06Y7MFUO1Vwt1Vt7Wo+Cpa3x2a1CmEkfFOi4WqP43VJnUtjjKmXoKRz3VUmqByyJYUAGbQ= -www.example.com. 3600 IN NSEC example.com. A RRSIG NSEC -www.example.com. 3600 IN RRSIG NSEC 8 3 3600 20201116135527 20201019135527 55566 example.com. meg/t6nIBqQZ0d5/dT7uu/3CuP4vE+HxqFQaj2fjUNceA/6C7QIQnqQ5Kyblg+XijDkQX0yvyFNHYdgF16UDgFT7tlNUCHk1SpF5BWzV4c4tBEhxASTz7UQo111O3Tyd6CldPzO/Se15Ud0/ZYltHEqWTfY5nJoXC/OJD9V2QOI= FILE_END SCENARIO_END