1 /* $NetBSD: notify.c,v 1.8 2025/01/26 16:25:45 christos Exp $ */ 2 3 /* 4 * Copyright (C) Internet Systems Consortium, Inc. ("ISC") 5 * 6 * SPDX-License-Identifier: MPL-2.0 7 * 8 * This Source Code Form is subject to the terms of the Mozilla Public 9 * License, v. 2.0. If a copy of the MPL was not distributed with this 10 * file, you can obtain one at https://mozilla.org/MPL/2.0/. 11 * 12 * See the COPYRIGHT file distributed with this work for additional 13 * information regarding copyright ownership. 14 */ 15 16 #include <isc/log.h> 17 #include <isc/result.h> 18 19 #include <dns/message.h> 20 #include <dns/rdataset.h> 21 #include <dns/result.h> 22 #include <dns/tsig.h> 23 #include <dns/view.h> 24 #include <dns/zone.h> 25 #include <dns/zt.h> 26 27 #include <ns/log.h> 28 #include <ns/notify.h> 29 #include <ns/types.h> 30 31 /*! \file 32 * \brief 33 * This module implements notify as in RFC1996. 34 */ 35 36 static void 37 notify_log(ns_client_t *client, int level, const char *fmt, ...) { 38 va_list ap; 39 40 va_start(ap, fmt); 41 ns_client_logv(client, DNS_LOGCATEGORY_NOTIFY, NS_LOGMODULE_NOTIFY, 42 level, fmt, ap); 43 va_end(ap); 44 } 45 46 static void 47 respond(ns_client_t *client, isc_result_t result) { 48 dns_rcode_t rcode; 49 dns_message_t *message; 50 isc_result_t msg_result; 51 52 message = client->message; 53 rcode = dns_result_torcode(result); 54 55 msg_result = dns_message_reply(message, true); 56 if (msg_result != ISC_R_SUCCESS) { 57 msg_result = dns_message_reply(message, false); 58 } 59 if (msg_result != ISC_R_SUCCESS) { 60 ns_client_drop(client, msg_result); 61 isc_nmhandle_detach(&client->reqhandle); 62 return; 63 } 64 message->rcode = rcode; 65 if (rcode == dns_rcode_noerror) { 66 message->flags |= DNS_MESSAGEFLAG_AA; 67 } else { 68 message->flags &= ~DNS_MESSAGEFLAG_AA; 69 } 70 71 ns_client_send(client); 72 isc_nmhandle_detach(&client->reqhandle); 73 } 74 75 void 76 ns_notify_start(ns_client_t *client, isc_nmhandle_t *handle) { 77 dns_message_t *request = client->message; 78 isc_result_t result; 79 dns_name_t *zonename; 80 dns_rdataset_t *zone_rdataset; 81 dns_zone_t *zone = NULL; 82 char namebuf[DNS_NAME_FORMATSIZE]; 83 char tsigbuf[DNS_NAME_FORMATSIZE * 2 + sizeof(": TSIG '' ()")]; 84 dns_tsigkey_t *tsigkey; 85 86 /* 87 * Attach to the request handle 88 */ 89 isc_nmhandle_attach(handle, &client->reqhandle); 90 91 /* 92 * Interpret the question section. 93 */ 94 result = dns_message_firstname(request, DNS_SECTION_QUESTION); 95 if (result != ISC_R_SUCCESS) { 96 notify_log(client, ISC_LOG_NOTICE, 97 "notify question section empty"); 98 result = DNS_R_FORMERR; 99 goto done; 100 } 101 102 /* 103 * The question section must contain exactly one question. 104 */ 105 zonename = NULL; 106 dns_message_currentname(request, DNS_SECTION_QUESTION, &zonename); 107 zone_rdataset = ISC_LIST_HEAD(zonename->list); 108 if (ISC_LIST_NEXT(zone_rdataset, link) != NULL) { 109 notify_log(client, ISC_LOG_NOTICE, 110 "notify question section contains multiple RRs"); 111 result = DNS_R_FORMERR; 112 goto done; 113 } 114 115 /* The zone section must have exactly one name. */ 116 result = dns_message_nextname(request, DNS_SECTION_ZONE); 117 if (result != ISC_R_NOMORE) { 118 notify_log(client, ISC_LOG_NOTICE, 119 "notify question section contains multiple RRs"); 120 result = DNS_R_FORMERR; 121 goto done; 122 } 123 124 /* The one rdataset must be an SOA. */ 125 if (zone_rdataset->type != dns_rdatatype_soa) { 126 notify_log(client, ISC_LOG_NOTICE, 127 "notify question section contains no SOA"); 128 result = DNS_R_FORMERR; 129 goto done; 130 } 131 132 tsigkey = dns_message_gettsigkey(request); 133 if (tsigkey != NULL) { 134 dns_name_format(tsigkey->name, namebuf, sizeof(namebuf)); 135 136 if (tsigkey->generated) { 137 char cnamebuf[DNS_NAME_FORMATSIZE]; 138 dns_name_format(tsigkey->creator, cnamebuf, 139 sizeof(cnamebuf)); 140 snprintf(tsigbuf, sizeof(tsigbuf), ": TSIG '%s' (%s)", 141 namebuf, cnamebuf); 142 } else { 143 snprintf(tsigbuf, sizeof(tsigbuf), ": TSIG '%s'", 144 namebuf); 145 } 146 } else { 147 tsigbuf[0] = '\0'; 148 } 149 150 dns_name_format(zonename, namebuf, sizeof(namebuf)); 151 result = dns_view_findzone(client->view, zonename, DNS_ZTFIND_EXACT, 152 &zone); 153 if (result == ISC_R_SUCCESS) { 154 dns_zonetype_t zonetype = dns_zone_gettype(zone); 155 156 if ((zonetype == dns_zone_primary) || 157 (zonetype == dns_zone_secondary) || 158 (zonetype == dns_zone_mirror) || 159 (zonetype == dns_zone_stub)) 160 { 161 isc_sockaddr_t *from = ns_client_getsockaddr(client); 162 isc_sockaddr_t *to = ns_client_getdestaddr(client); 163 notify_log(client, ISC_LOG_INFO, 164 "received notify for zone '%s'%s", namebuf, 165 tsigbuf); 166 result = dns_zone_notifyreceive(zone, from, to, 167 request); 168 goto done; 169 } 170 } 171 172 result = DNS_R_NOTAUTH; 173 notify_log(client, ISC_LOG_NOTICE, 174 "received notify for zone '%s'%s: %s", namebuf, tsigbuf, 175 isc_result_totext(result)); 176 177 done: 178 if (zone != NULL) { 179 dns_zone_detach(&zone); 180 } 181 respond(client, result); 182 } 183