xref: /netbsd-src/external/mpl/bind/dist/lib/ns/listenlist.c (revision bcda20f65a8566e103791ec395f7f499ef322704)
1 /*	$NetBSD: listenlist.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 /*! \file */
17 
18 #include <stdbool.h>
19 
20 #include <isc/mem.h>
21 #include <isc/netmgr.h>
22 #include <isc/util.h>
23 
24 #include <dns/acl.h>
25 
26 #include <ns/listenlist.h>
27 #include <ns/log.h>
28 
29 static void
30 destroy(ns_listenlist_t *list);
31 
32 static isc_result_t
33 listenelt_create(isc_mem_t *mctx, in_port_t port, dns_acl_t *acl,
34 		 const uint16_t family, const bool is_http, bool tls,
35 		 const ns_listen_tls_params_t *tls_params,
36 		 isc_tlsctx_cache_t *tlsctx_cache, isc_nm_proxy_type_t proxy,
37 		 ns_listenelt_t **target) {
38 	ns_listenelt_t *elt = NULL;
39 	isc_result_t result = ISC_R_SUCCESS;
40 	isc_tlsctx_t *sslctx = NULL;
41 	isc_tls_cert_store_t *store = NULL, *found_store = NULL;
42 
43 	REQUIRE(target != NULL && *target == NULL);
44 	REQUIRE(!tls || (tls_params != NULL && tlsctx_cache != NULL));
45 
46 	if (tls) {
47 		const isc_tlsctx_cache_transport_t transport =
48 			is_http ? isc_tlsctx_cache_https : isc_tlsctx_cache_tls;
49 
50 		/*
51 		 * Let's try to reuse the existing context from the cache in
52 		 * order to avoid excessive TLS contexts creation.
53 		 */
54 		result = isc_tlsctx_cache_find(tlsctx_cache, tls_params->name,
55 					       transport, family, &sslctx,
56 					       &found_store, NULL);
57 		if (result != ISC_R_SUCCESS) {
58 			/*
59 			 * The lookup failed, let's try to create a new context
60 			 * and store it within the cache.
61 			 */
62 			INSIST(tls_params->name != NULL &&
63 			       *tls_params->name != '\0');
64 
65 			result = isc_tlsctx_createserver(
66 				tls_params->key, tls_params->cert, &sslctx);
67 			if (result != ISC_R_SUCCESS) {
68 				goto tls_error;
69 			}
70 
71 			/*
72 			 * We need to initialise session ID context to make TLS
73 			 * session resumption work correctly - in particular in
74 			 * the case when client certificates are used (Mutual
75 			 * TLS) - otherwise resumption attempts will lead to
76 			 * handshake failures. See OpenSSL documentation for
77 			 * 'SSL_CTX_set_session_id_context()', the "Warnings"
78 			 * section.
79 			 */
80 			isc_tlsctx_set_random_session_id_context(sslctx);
81 
82 			/*
83 			 * If CA-bundle file is specified - enable client
84 			 * certificates validation.
85 			 */
86 			if (tls_params->ca_file != NULL) {
87 				if (found_store == NULL) {
88 					result = isc_tls_cert_store_create(
89 						tls_params->ca_file, &store);
90 					if (result != ISC_R_SUCCESS) {
91 						goto tls_error;
92 					}
93 				} else {
94 					store = found_store;
95 				}
96 
97 				result = isc_tlsctx_enable_peer_verification(
98 					sslctx, true, store, NULL, false);
99 				if (result != ISC_R_SUCCESS) {
100 					goto tls_error;
101 				}
102 
103 				/*
104 				 * Load the list of allowed client certificate
105 				 * issuers to send to TLS clients.
106 				 */
107 				result = isc_tlsctx_load_client_ca_names(
108 					sslctx, tls_params->ca_file);
109 				if (result != ISC_R_SUCCESS) {
110 					goto tls_error;
111 				}
112 			}
113 
114 			if (tls_params->protocols != 0) {
115 				isc_tlsctx_set_protocols(sslctx,
116 							 tls_params->protocols);
117 			}
118 
119 			if (tls_params->dhparam_file != NULL) {
120 				if (!isc_tlsctx_load_dhparams(
121 					    sslctx, tls_params->dhparam_file))
122 				{
123 					isc_log_write(ns_lctx,
124 						      NS_LOGCATEGORY_GENERAL,
125 						      NS_LOGMODULE_INTERFACEMGR,
126 						      ISC_LOG_ERROR,
127 						      "loading of dhparam-file "
128 						      "'%s' failed",
129 						      tls_params->dhparam_file);
130 					result = ISC_R_FAILURE;
131 					goto tls_error;
132 				}
133 			}
134 
135 			if (tls_params->ciphers != NULL) {
136 				isc_tlsctx_set_cipherlist(sslctx,
137 							  tls_params->ciphers);
138 			}
139 
140 			if (tls_params->cipher_suites != NULL) {
141 				isc_tlsctx_set_cipher_suites(
142 					sslctx, tls_params->cipher_suites);
143 			}
144 
145 			if (tls_params->prefer_server_ciphers_set) {
146 				isc_tlsctx_prefer_server_ciphers(
147 					sslctx,
148 					tls_params->prefer_server_ciphers);
149 			}
150 
151 			if (tls_params->session_tickets_set) {
152 				isc_tlsctx_session_tickets(
153 					sslctx, tls_params->session_tickets);
154 			}
155 
156 #ifdef HAVE_LIBNGHTTP2
157 			if (is_http) {
158 				isc_tlsctx_enable_http2server_alpn(sslctx);
159 			}
160 #endif /* HAVE_LIBNGHTTP2 */
161 
162 			if (!is_http) {
163 				isc_tlsctx_enable_dot_server_alpn(sslctx);
164 			}
165 
166 			/*
167 			 * The storing in the cache should not fail because the
168 			 * (re)initialisation happens from within a single
169 			 * thread.
170 			 *
171 			 * Taking into account that the most recent call to
172 			 * 'isc_tlsctx_cache_find()' has failed, it means that
173 			 * the TLS context has not been found. Considering that
174 			 * the initialisation happens from within the context of
175 			 * a single thread, the call to 'isc_tlsctx_cache_add()'
176 			 * is expected not to fail.
177 			 */
178 			RUNTIME_CHECK(isc_tlsctx_cache_add(
179 					      tlsctx_cache, tls_params->name,
180 					      transport, family, sslctx, store,
181 					      NULL, NULL, NULL,
182 					      NULL) == ISC_R_SUCCESS);
183 		} else {
184 			INSIST(sslctx != NULL);
185 		}
186 	}
187 
188 	elt = isc_mem_get(mctx, sizeof(*elt));
189 	elt->mctx = mctx;
190 	ISC_LINK_INIT(elt, link);
191 	elt->port = port;
192 	elt->is_http = false;
193 	elt->acl = acl;
194 	elt->sslctx = sslctx;
195 	elt->sslctx_cache = NULL;
196 	if (sslctx != NULL && tlsctx_cache != NULL) {
197 		isc_tlsctx_cache_attach(tlsctx_cache, &elt->sslctx_cache);
198 	}
199 	elt->http_endpoints = NULL;
200 	elt->http_endpoints_number = 0;
201 	elt->http_max_clients = 0;
202 	elt->max_concurrent_streams = 0;
203 	elt->proxy = proxy;
204 
205 	*target = elt;
206 	return ISC_R_SUCCESS;
207 tls_error:
208 	if (sslctx != NULL) {
209 		isc_tlsctx_free(&sslctx);
210 	}
211 
212 	if (store != NULL && store != found_store) {
213 		isc_tls_cert_store_free(&store);
214 	}
215 	return result;
216 }
217 
218 isc_result_t
219 ns_listenelt_create(isc_mem_t *mctx, in_port_t port, dns_acl_t *acl,
220 		    const uint16_t family, bool tls,
221 		    const ns_listen_tls_params_t *tls_params,
222 		    isc_tlsctx_cache_t *tlsctx_cache, isc_nm_proxy_type_t proxy,
223 		    ns_listenelt_t **target) {
224 	return listenelt_create(mctx, port, acl, family, false, tls, tls_params,
225 				tlsctx_cache, proxy, target);
226 }
227 
228 isc_result_t
229 ns_listenelt_create_http(isc_mem_t *mctx, in_port_t http_port, dns_acl_t *acl,
230 			 const uint16_t family, bool tls,
231 			 const ns_listen_tls_params_t *tls_params,
232 			 isc_tlsctx_cache_t *tlsctx_cache,
233 			 isc_nm_proxy_type_t proxy, char **endpoints,
234 			 size_t nendpoints, const uint32_t max_clients,
235 			 const uint32_t max_streams, ns_listenelt_t **target) {
236 	isc_result_t result;
237 
238 	REQUIRE(target != NULL && *target == NULL);
239 	REQUIRE(endpoints != NULL && *endpoints != NULL);
240 	REQUIRE(nendpoints > 0);
241 
242 	result = listenelt_create(mctx, http_port, acl, family, true, tls,
243 				  tls_params, tlsctx_cache, proxy, target);
244 	if (result == ISC_R_SUCCESS) {
245 		(*target)->is_http = true;
246 		(*target)->http_endpoints = endpoints;
247 		(*target)->http_endpoints_number = nendpoints;
248 		/*
249 		 * 0 sized quota - means unlimited quota. We used to not
250 		 * create a quota object in such a case, but we might need to
251 		 * update the value of the quota during reconfiguration, so we
252 		 * need to have a quota object in place anyway.
253 		 */
254 		(*target)->http_max_clients = max_clients == 0 ? UINT32_MAX
255 							       : max_clients;
256 		(*target)->max_concurrent_streams = max_streams;
257 	} else {
258 		size_t i;
259 		for (i = 0; i < nendpoints; i++) {
260 			isc_mem_free(mctx, endpoints[i]);
261 		}
262 		isc_mem_free(mctx, endpoints);
263 	}
264 	return result;
265 }
266 
267 void
268 ns_listenelt_destroy(ns_listenelt_t *elt) {
269 	if (elt->acl != NULL) {
270 		dns_acl_detach(&elt->acl);
271 	}
272 
273 	elt->sslctx = NULL; /* this one is going to be destroyed alongside the
274 			       sslctx_cache */
275 	if (elt->sslctx_cache != NULL) {
276 		isc_tlsctx_cache_detach(&elt->sslctx_cache);
277 	}
278 	if (elt->http_endpoints != NULL) {
279 		size_t i;
280 		INSIST(elt->http_endpoints_number > 0);
281 		for (i = 0; i < elt->http_endpoints_number; i++) {
282 			isc_mem_free(elt->mctx, elt->http_endpoints[i]);
283 		}
284 		isc_mem_free(elt->mctx, elt->http_endpoints);
285 	}
286 	isc_mem_put(elt->mctx, elt, sizeof(*elt));
287 }
288 
289 isc_result_t
290 ns_listenlist_create(isc_mem_t *mctx, ns_listenlist_t **target) {
291 	ns_listenlist_t *list = NULL;
292 	REQUIRE(target != NULL && *target == NULL);
293 	list = isc_mem_get(mctx, sizeof(*list));
294 	list->mctx = mctx;
295 	list->refcount = 1;
296 	ISC_LIST_INIT(list->elts);
297 	*target = list;
298 	return ISC_R_SUCCESS;
299 }
300 
301 static void
302 destroy(ns_listenlist_t *list) {
303 	ns_listenelt_t *elt, *next;
304 	for (elt = ISC_LIST_HEAD(list->elts); elt != NULL; elt = next) {
305 		next = ISC_LIST_NEXT(elt, link);
306 		ns_listenelt_destroy(elt);
307 	}
308 	isc_mem_put(list->mctx, list, sizeof(*list));
309 }
310 
311 void
312 ns_listenlist_attach(ns_listenlist_t *source, ns_listenlist_t **target) {
313 	INSIST(source->refcount > 0);
314 	source->refcount++;
315 	*target = source;
316 }
317 
318 void
319 ns_listenlist_detach(ns_listenlist_t **listp) {
320 	ns_listenlist_t *list = *listp;
321 	*listp = NULL;
322 	INSIST(list->refcount > 0);
323 	list->refcount--;
324 	if (list->refcount == 0) {
325 		destroy(list);
326 	}
327 }
328 
329 isc_result_t
330 ns_listenlist_default(isc_mem_t *mctx, in_port_t port, bool enabled,
331 		      const uint16_t family, ns_listenlist_t **target) {
332 	isc_result_t result;
333 	dns_acl_t *acl = NULL;
334 	ns_listenelt_t *elt = NULL;
335 	ns_listenlist_t *list = NULL;
336 
337 	REQUIRE(target != NULL && *target == NULL);
338 	if (enabled) {
339 		result = dns_acl_any(mctx, &acl);
340 	} else {
341 		result = dns_acl_none(mctx, &acl);
342 	}
343 	if (result != ISC_R_SUCCESS) {
344 		goto cleanup;
345 	}
346 
347 	result = ns_listenelt_create(mctx, port, acl, family, false, NULL, NULL,
348 				     ISC_NM_PROXY_NONE, &elt);
349 	if (result != ISC_R_SUCCESS) {
350 		goto cleanup_acl;
351 	}
352 
353 	result = ns_listenlist_create(mctx, &list);
354 	if (result != ISC_R_SUCCESS) {
355 		goto cleanup_listenelt;
356 	}
357 
358 	ISC_LIST_APPEND(list->elts, elt, link);
359 
360 	*target = list;
361 	return ISC_R_SUCCESS;
362 
363 cleanup_listenelt:
364 	ns_listenelt_destroy(elt);
365 cleanup_acl:
366 	dns_acl_detach(&acl);
367 cleanup:
368 	return result;
369 }
370