Home | History | Annotate | Line # | Download | only in ns
      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