1/*	$NetBSD: ssu_external.c,v 1.7 2024/02/21 22:52:08 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/*
17 * This implements external update-policy rules.  This allows permission
18 * to update a zone to be checked by consulting an external daemon (e.g.,
19 * kerberos).
20 */
21
22#include <errno.h>
23#include <inttypes.h>
24#include <stdbool.h>
25#include <sys/socket.h>
26#include <sys/un.h>
27#include <unistd.h>
28
29#include <isc/magic.h>
30#include <isc/mem.h>
31#include <isc/netaddr.h>
32#include <isc/print.h>
33#include <isc/result.h>
34#include <isc/strerr.h>
35#include <isc/string.h>
36#include <isc/util.h>
37
38#include <dns/fixedname.h>
39#include <dns/log.h>
40#include <dns/name.h>
41#include <dns/rdatatype.h>
42#include <dns/ssu.h>
43
44#include <dst/dst.h>
45
46static void
47ssu_e_log(int level, const char *fmt, ...) {
48	va_list ap;
49
50	va_start(ap, fmt);
51	isc_log_vwrite(dns_lctx, DNS_LOGCATEGORY_SECURITY, DNS_LOGMODULE_ZONE,
52		       ISC_LOG_DEBUG(level), fmt, ap);
53	va_end(ap);
54}
55
56/*
57 * Connect to a UNIX domain socket.
58 */
59static int
60ux_socket_connect(const char *path) {
61	int fd = -1;
62	struct sockaddr_un addr;
63
64	REQUIRE(path != NULL);
65
66	if (strlen(path) > sizeof(addr.sun_path)) {
67		ssu_e_log(3,
68			  "ssu_external: socket path '%s' "
69			  "longer than system maximum %zu",
70			  path, sizeof(addr.sun_path));
71		return (-1);
72	}
73
74	memset(&addr, 0, sizeof(addr));
75	addr.sun_family = AF_UNIX;
76	strlcpy(addr.sun_path, path, sizeof(addr.sun_path));
77
78	fd = socket(AF_UNIX, SOCK_STREAM, 0);
79	if (fd == -1) {
80		char strbuf[ISC_STRERRORSIZE];
81		strerror_r(errno, strbuf, sizeof(strbuf));
82		ssu_e_log(3, "ssu_external: unable to create socket - %s",
83			  strbuf);
84		return (-1);
85	}
86
87	if (connect(fd, (struct sockaddr *)&addr, sizeof(addr)) == -1) {
88		char strbuf[ISC_STRERRORSIZE];
89		strerror_r(errno, strbuf, sizeof(strbuf));
90		ssu_e_log(3,
91			  "ssu_external: unable to connect to "
92			  "socket '%s' - %s",
93			  path, strbuf);
94		close(fd);
95		return (-1);
96	}
97	return (fd);
98}
99
100/* Change this version if you update the format of the request */
101#define SSU_EXTERNAL_VERSION 1
102
103/*
104 * Perform an update-policy rule check against an external application
105 * over a socket.
106 *
107 * This currently only supports local: for unix domain datagram sockets.
108 *
109 * Note that by using a datagram socket and creating a new socket each
110 * time we avoid the need for locking and allow for parallel access to
111 * the authorization server.
112 */
113bool
114dns_ssu_external_match(const dns_name_t *identity, const dns_name_t *signer,
115		       const dns_name_t *name, const isc_netaddr_t *tcpaddr,
116		       dns_rdatatype_t type, const dst_key_t *key,
117		       isc_mem_t *mctx) {
118	char b_identity[DNS_NAME_FORMATSIZE];
119	char b_signer[DNS_NAME_FORMATSIZE];
120	char b_name[DNS_NAME_FORMATSIZE];
121	char b_addr[ISC_NETADDR_FORMATSIZE];
122	char b_type[DNS_RDATATYPE_FORMATSIZE];
123	char b_key[DST_KEY_FORMATSIZE];
124	isc_buffer_t *tkey_token = NULL;
125	int fd;
126	const char *sock_path;
127	unsigned int req_len;
128	isc_region_t token_region = { NULL, 0 };
129	unsigned char *data;
130	isc_buffer_t buf;
131	uint32_t token_len = 0;
132	uint32_t reply;
133	ssize_t ret;
134
135	/* The identity contains local:/path/to/socket */
136	dns_name_format(identity, b_identity, sizeof(b_identity));
137
138	/* For now only local: is supported */
139	if (strncmp(b_identity, "local:", 6) != 0) {
140		ssu_e_log(3, "ssu_external: invalid socket path '%s'",
141			  b_identity);
142		return (false);
143	}
144	sock_path = &b_identity[6];
145
146	fd = ux_socket_connect(sock_path);
147	if (fd == -1) {
148		return (false);
149	}
150
151	if (key != NULL) {
152		dst_key_format(key, b_key, sizeof(b_key));
153		tkey_token = dst_key_tkeytoken(key);
154	} else {
155		b_key[0] = 0;
156	}
157
158	if (tkey_token != NULL) {
159		isc_buffer_region(tkey_token, &token_region);
160		token_len = token_region.length;
161	}
162
163	/* Format the request elements */
164	if (signer != NULL) {
165		dns_name_format(signer, b_signer, sizeof(b_signer));
166	} else {
167		b_signer[0] = 0;
168	}
169
170	dns_name_format(name, b_name, sizeof(b_name));
171
172	if (tcpaddr != NULL) {
173		isc_netaddr_format(tcpaddr, b_addr, sizeof(b_addr));
174	} else {
175		b_addr[0] = 0;
176	}
177
178	dns_rdatatype_format(type, b_type, sizeof(b_type));
179
180	/* Work out how big the request will be */
181	req_len = sizeof(uint32_t) +	 /* Format version */
182		  sizeof(uint32_t) +	 /* Length */
183		  strlen(b_signer) + 1 + /* Signer */
184		  strlen(b_name) + 1 +	 /* Name */
185		  strlen(b_addr) + 1 +	 /* Address */
186		  strlen(b_type) + 1 +	 /* Type */
187		  strlen(b_key) + 1 +	 /* Key */
188		  sizeof(uint32_t) +	 /* tkey_token length */
189		  token_len;		 /* tkey_token */
190
191	/* format the buffer */
192	data = isc_mem_allocate(mctx, req_len);
193
194	isc_buffer_init(&buf, data, req_len);
195	isc_buffer_putuint32(&buf, SSU_EXTERNAL_VERSION);
196	isc_buffer_putuint32(&buf, req_len);
197
198	/* Strings must be null-terminated */
199	isc_buffer_putstr(&buf, b_signer);
200	isc_buffer_putuint8(&buf, 0);
201	isc_buffer_putstr(&buf, b_name);
202	isc_buffer_putuint8(&buf, 0);
203	isc_buffer_putstr(&buf, b_addr);
204	isc_buffer_putuint8(&buf, 0);
205	isc_buffer_putstr(&buf, b_type);
206	isc_buffer_putuint8(&buf, 0);
207	isc_buffer_putstr(&buf, b_key);
208	isc_buffer_putuint8(&buf, 0);
209
210	isc_buffer_putuint32(&buf, token_len);
211	if (tkey_token && token_len != 0) {
212		isc_buffer_putmem(&buf, token_region.base, token_len);
213	}
214
215	ENSURE(isc_buffer_availablelength(&buf) == 0);
216
217	/* Send the request */
218	ret = write(fd, data, req_len);
219	isc_mem_free(mctx, data);
220	if (ret != (ssize_t)req_len) {
221		char strbuf[ISC_STRERRORSIZE];
222		strerror_r(errno, strbuf, sizeof(strbuf));
223		ssu_e_log(3, "ssu_external: unable to send request - %s",
224			  strbuf);
225		close(fd);
226		return (false);
227	}
228
229	/* Receive the reply */
230	ret = read(fd, &reply, sizeof(uint32_t));
231	if (ret != (ssize_t)sizeof(uint32_t)) {
232		char strbuf[ISC_STRERRORSIZE];
233		strerror_r(errno, strbuf, sizeof(strbuf));
234		ssu_e_log(3, "ssu_external: unable to receive reply - %s",
235			  strbuf);
236		close(fd);
237		return (false);
238	}
239
240	close(fd);
241
242	reply = ntohl(reply);
243
244	if (reply == 0) {
245		ssu_e_log(3, "ssu_external: denied external auth for '%s'",
246			  b_name);
247		return (false);
248	} else if (reply == 1) {
249		ssu_e_log(3, "ssu_external: allowed external auth for '%s'",
250			  b_name);
251		return (true);
252	}
253
254	ssu_e_log(3, "ssu_external: invalid reply 0x%08x", reply);
255
256	return (false);
257}
258