ssu.c revision 1.7
1/*	$NetBSD: ssu.c,v 1.7 2023/01/25 21:43:30 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/magic.h>
21#include <isc/mem.h>
22#include <isc/netaddr.h>
23#include <isc/print.h>
24#include <isc/refcount.h>
25#include <isc/result.h>
26#include <isc/string.h>
27#include <isc/util.h>
28
29#include <dns/dlz.h>
30#include <dns/fixedname.h>
31#include <dns/name.h>
32#include <dns/ssu.h>
33
34#include <dst/dst.h>
35#include <dst/gssapi.h>
36
37#define SSUTABLEMAGIC	      ISC_MAGIC('S', 'S', 'U', 'T')
38#define VALID_SSUTABLE(table) ISC_MAGIC_VALID(table, SSUTABLEMAGIC)
39
40#define SSURULEMAGIC	     ISC_MAGIC('S', 'S', 'U', 'R')
41#define VALID_SSURULE(table) ISC_MAGIC_VALID(table, SSURULEMAGIC)
42
43struct dns_ssurule {
44	unsigned int magic;
45	bool grant;		      /*%< is this a grant or a deny? */
46	dns_ssumatchtype_t matchtype; /*%< which type of pattern match?
47				       * */
48	dns_name_t *identity;	      /*%< the identity to match */
49	dns_name_t *name;	      /*%< the name being updated */
50	unsigned int ntypes;	      /*%< number of data types covered */
51	dns_rdatatype_t *types;	      /*%< the data types.  Can include */
52				      /*   ANY. if NULL, defaults to all */
53				      /*   types except SIG, SOA, and NS */
54	ISC_LINK(dns_ssurule_t) link;
55};
56
57struct dns_ssutable {
58	unsigned int magic;
59	isc_mem_t *mctx;
60	isc_refcount_t references;
61	dns_dlzdb_t *dlzdatabase;
62	ISC_LIST(dns_ssurule_t) rules;
63};
64
65isc_result_t
66dns_ssutable_create(isc_mem_t *mctx, dns_ssutable_t **tablep) {
67	dns_ssutable_t *table;
68
69	REQUIRE(tablep != NULL && *tablep == NULL);
70	REQUIRE(mctx != NULL);
71
72	table = isc_mem_get(mctx, sizeof(dns_ssutable_t));
73	isc_refcount_init(&table->references, 1);
74	table->mctx = NULL;
75	isc_mem_attach(mctx, &table->mctx);
76	ISC_LIST_INIT(table->rules);
77	table->magic = SSUTABLEMAGIC;
78	*tablep = table;
79	return (ISC_R_SUCCESS);
80}
81
82static void
83destroy(dns_ssutable_t *table) {
84	isc_mem_t *mctx;
85
86	REQUIRE(VALID_SSUTABLE(table));
87
88	mctx = table->mctx;
89	while (!ISC_LIST_EMPTY(table->rules)) {
90		dns_ssurule_t *rule = ISC_LIST_HEAD(table->rules);
91		if (rule->identity != NULL) {
92			dns_name_free(rule->identity, mctx);
93			isc_mem_put(mctx, rule->identity, sizeof(dns_name_t));
94		}
95		if (rule->name != NULL) {
96			dns_name_free(rule->name, mctx);
97			isc_mem_put(mctx, rule->name, sizeof(dns_name_t));
98		}
99		if (rule->types != NULL) {
100			isc_mem_put(mctx, rule->types,
101				    rule->ntypes * sizeof(dns_rdatatype_t));
102		}
103		ISC_LIST_UNLINK(table->rules, rule, link);
104		rule->magic = 0;
105		isc_mem_put(mctx, rule, sizeof(dns_ssurule_t));
106	}
107	isc_refcount_destroy(&table->references);
108	table->magic = 0;
109	isc_mem_putanddetach(&table->mctx, table, sizeof(dns_ssutable_t));
110}
111
112void
113dns_ssutable_attach(dns_ssutable_t *source, dns_ssutable_t **targetp) {
114	REQUIRE(VALID_SSUTABLE(source));
115	REQUIRE(targetp != NULL && *targetp == NULL);
116
117	isc_refcount_increment(&source->references);
118
119	*targetp = source;
120}
121
122void
123dns_ssutable_detach(dns_ssutable_t **tablep) {
124	dns_ssutable_t *table;
125
126	REQUIRE(tablep != NULL);
127	table = *tablep;
128	*tablep = NULL;
129	REQUIRE(VALID_SSUTABLE(table));
130
131	if (isc_refcount_decrement(&table->references) == 1) {
132		destroy(table);
133	}
134}
135
136isc_result_t
137dns_ssutable_addrule(dns_ssutable_t *table, bool grant,
138		     const dns_name_t *identity, dns_ssumatchtype_t matchtype,
139		     const dns_name_t *name, unsigned int ntypes,
140		     dns_rdatatype_t *types) {
141	dns_ssurule_t *rule;
142	isc_mem_t *mctx;
143
144	REQUIRE(VALID_SSUTABLE(table));
145	REQUIRE(dns_name_isabsolute(identity));
146	REQUIRE(dns_name_isabsolute(name));
147	REQUIRE(matchtype <= dns_ssumatchtype_max);
148	if (matchtype == dns_ssumatchtype_wildcard) {
149		REQUIRE(dns_name_iswildcard(name));
150	}
151	if (ntypes > 0) {
152		REQUIRE(types != NULL);
153	}
154
155	mctx = table->mctx;
156	rule = isc_mem_get(mctx, sizeof(dns_ssurule_t));
157
158	rule->identity = NULL;
159	rule->name = NULL;
160	rule->types = NULL;
161
162	rule->grant = grant;
163
164	rule->identity = isc_mem_get(mctx, sizeof(dns_name_t));
165	dns_name_init(rule->identity, NULL);
166	dns_name_dup(identity, mctx, rule->identity);
167
168	rule->name = isc_mem_get(mctx, sizeof(dns_name_t));
169	dns_name_init(rule->name, NULL);
170	dns_name_dup(name, mctx, rule->name);
171
172	rule->matchtype = matchtype;
173
174	rule->ntypes = ntypes;
175	if (ntypes > 0) {
176		rule->types = isc_mem_get(mctx,
177					  ntypes * sizeof(dns_rdatatype_t));
178		memmove(rule->types, types, ntypes * sizeof(dns_rdatatype_t));
179	} else {
180		rule->types = NULL;
181	}
182
183	rule->magic = SSURULEMAGIC;
184	ISC_LIST_INITANDAPPEND(table->rules, rule, link);
185
186	return (ISC_R_SUCCESS);
187}
188
189static bool
190isusertype(dns_rdatatype_t type) {
191	return (type != dns_rdatatype_ns && type != dns_rdatatype_soa &&
192		type != dns_rdatatype_rrsig);
193}
194
195static void
196reverse_from_address(dns_name_t *tcpself, const isc_netaddr_t *tcpaddr) {
197	char buf[16 * 4 + sizeof("IP6.ARPA.")];
198	isc_result_t result;
199	const unsigned char *ap;
200	isc_buffer_t b;
201	unsigned long l;
202
203	switch (tcpaddr->family) {
204	case AF_INET:
205		l = ntohl(tcpaddr->type.in.s_addr);
206		result = snprintf(buf, sizeof(buf),
207				  "%lu.%lu.%lu.%lu.IN-ADDR.ARPA.",
208				  (l >> 0) & 0xff, (l >> 8) & 0xff,
209				  (l >> 16) & 0xff, (l >> 24) & 0xff);
210		RUNTIME_CHECK(result < sizeof(buf));
211		break;
212	case AF_INET6:
213		ap = tcpaddr->type.in6.s6_addr;
214		result = snprintf(
215			buf, sizeof(buf),
216			"%x.%x.%x.%x.%x.%x.%x.%x."
217			"%x.%x.%x.%x.%x.%x.%x.%x."
218			"%x.%x.%x.%x.%x.%x.%x.%x."
219			"%x.%x.%x.%x.%x.%x.%x.%x."
220			"IP6.ARPA.",
221			ap[15] & 0x0f, (ap[15] >> 4) & 0x0f, ap[14] & 0x0f,
222			(ap[14] >> 4) & 0x0f, ap[13] & 0x0f,
223			(ap[13] >> 4) & 0x0f, ap[12] & 0x0f,
224			(ap[12] >> 4) & 0x0f, ap[11] & 0x0f,
225			(ap[11] >> 4) & 0x0f, ap[10] & 0x0f,
226			(ap[10] >> 4) & 0x0f, ap[9] & 0x0f, (ap[9] >> 4) & 0x0f,
227			ap[8] & 0x0f, (ap[8] >> 4) & 0x0f, ap[7] & 0x0f,
228			(ap[7] >> 4) & 0x0f, ap[6] & 0x0f, (ap[6] >> 4) & 0x0f,
229			ap[5] & 0x0f, (ap[5] >> 4) & 0x0f, ap[4] & 0x0f,
230			(ap[4] >> 4) & 0x0f, ap[3] & 0x0f, (ap[3] >> 4) & 0x0f,
231			ap[2] & 0x0f, (ap[2] >> 4) & 0x0f, ap[1] & 0x0f,
232			(ap[1] >> 4) & 0x0f, ap[0] & 0x0f, (ap[0] >> 4) & 0x0f);
233		RUNTIME_CHECK(result < sizeof(buf));
234		break;
235	default:
236		UNREACHABLE();
237	}
238	isc_buffer_init(&b, buf, strlen(buf));
239	isc_buffer_add(&b, strlen(buf));
240	result = dns_name_fromtext(tcpself, &b, dns_rootname, 0, NULL);
241	RUNTIME_CHECK(result == ISC_R_SUCCESS);
242}
243
244static void
245stf_from_address(dns_name_t *stfself, const isc_netaddr_t *tcpaddr) {
246	char buf[sizeof("X.X.X.X.Y.Y.Y.Y.2.0.0.2.IP6.ARPA.")];
247	isc_result_t result;
248	const unsigned char *ap;
249	isc_buffer_t b;
250	unsigned long l;
251
252	switch (tcpaddr->family) {
253	case AF_INET:
254		l = ntohl(tcpaddr->type.in.s_addr);
255		result = snprintf(buf, sizeof(buf),
256				  "%lx.%lx.%lx.%lx.%lx.%lx.%lx.%lx"
257				  "2.0.0.2.IP6.ARPA.",
258				  l & 0xf, (l >> 4) & 0xf, (l >> 8) & 0xf,
259				  (l >> 12) & 0xf, (l >> 16) & 0xf,
260				  (l >> 20) & 0xf, (l >> 24) & 0xf,
261				  (l >> 28) & 0xf);
262		RUNTIME_CHECK(result < sizeof(buf));
263		break;
264	case AF_INET6:
265		ap = tcpaddr->type.in6.s6_addr;
266		result = snprintf(
267			buf, sizeof(buf),
268			"%x.%x.%x.%x.%x.%x.%x.%x."
269			"%x.%x.%x.%x.IP6.ARPA.",
270			ap[5] & 0x0f, (ap[5] >> 4) & 0x0f, ap[4] & 0x0f,
271			(ap[4] >> 4) & 0x0f, ap[3] & 0x0f, (ap[3] >> 4) & 0x0f,
272			ap[2] & 0x0f, (ap[2] >> 4) & 0x0f, ap[1] & 0x0f,
273			(ap[1] >> 4) & 0x0f, ap[0] & 0x0f, (ap[0] >> 4) & 0x0f);
274		RUNTIME_CHECK(result < sizeof(buf));
275		break;
276	default:
277		UNREACHABLE();
278	}
279	isc_buffer_init(&b, buf, strlen(buf));
280	isc_buffer_add(&b, strlen(buf));
281	result = dns_name_fromtext(stfself, &b, dns_rootname, 0, NULL);
282	RUNTIME_CHECK(result == ISC_R_SUCCESS);
283}
284
285bool
286dns_ssutable_checkrules(dns_ssutable_t *table, const dns_name_t *signer,
287			const dns_name_t *name, const isc_netaddr_t *addr,
288			bool tcp, const dns_aclenv_t *env, dns_rdatatype_t type,
289			const dst_key_t *key) {
290	dns_ssurule_t *rule;
291	unsigned int i;
292	dns_fixedname_t fixed;
293	dns_name_t *wildcard;
294	dns_name_t *tcpself;
295	dns_name_t *stfself;
296	isc_result_t result;
297	int match;
298
299	REQUIRE(VALID_SSUTABLE(table));
300	REQUIRE(signer == NULL || dns_name_isabsolute(signer));
301	REQUIRE(dns_name_isabsolute(name));
302	REQUIRE(addr == NULL || env != NULL);
303
304	if (signer == NULL && addr == NULL) {
305		return (false);
306	}
307
308	for (rule = ISC_LIST_HEAD(table->rules); rule != NULL;
309	     rule = ISC_LIST_NEXT(rule, link))
310	{
311		switch (rule->matchtype) {
312		case dns_ssumatchtype_name:
313		case dns_ssumatchtype_local:
314		case dns_ssumatchtype_subdomain:
315		case dns_ssumatchtype_wildcard:
316		case dns_ssumatchtype_self:
317		case dns_ssumatchtype_selfsub:
318		case dns_ssumatchtype_selfwild:
319			if (signer == NULL) {
320				continue;
321			}
322			if (dns_name_iswildcard(rule->identity)) {
323				if (!dns_name_matcheswildcard(signer,
324							      rule->identity))
325				{
326					continue;
327				}
328			} else {
329				if (!dns_name_equal(signer, rule->identity)) {
330					continue;
331				}
332			}
333			break;
334		case dns_ssumatchtype_selfkrb5:
335		case dns_ssumatchtype_selfms:
336		case dns_ssumatchtype_selfsubkrb5:
337		case dns_ssumatchtype_selfsubms:
338		case dns_ssumatchtype_subdomainkrb5:
339		case dns_ssumatchtype_subdomainms:
340			if (signer == NULL) {
341				continue;
342			}
343			break;
344		case dns_ssumatchtype_tcpself:
345		case dns_ssumatchtype_6to4self:
346			if (!tcp || addr == NULL) {
347				continue;
348			}
349			break;
350		case dns_ssumatchtype_external:
351		case dns_ssumatchtype_dlz:
352			break;
353		}
354
355		switch (rule->matchtype) {
356		case dns_ssumatchtype_name:
357			if (!dns_name_equal(name, rule->name)) {
358				continue;
359			}
360			break;
361		case dns_ssumatchtype_subdomain:
362			if (!dns_name_issubdomain(name, rule->name)) {
363				continue;
364			}
365			break;
366		case dns_ssumatchtype_local:
367			if (addr == NULL) {
368				continue;
369			}
370			if (!dns_name_issubdomain(name, rule->name)) {
371				continue;
372			}
373			dns_acl_match(addr, NULL, env->localhost, NULL, &match,
374				      NULL);
375			if (match == 0) {
376				if (signer != NULL) {
377					isc_log_write(dns_lctx,
378						      DNS_LOGCATEGORY_GENERAL,
379						      DNS_LOGMODULE_SSU,
380						      ISC_LOG_WARNING,
381						      "update-policy local: "
382						      "match on session "
383						      "key not from "
384						      "localhost");
385				}
386				continue;
387			}
388			break;
389		case dns_ssumatchtype_wildcard:
390			if (!dns_name_matcheswildcard(name, rule->name)) {
391				continue;
392			}
393			break;
394		case dns_ssumatchtype_self:
395			if (!dns_name_equal(signer, name)) {
396				continue;
397			}
398			break;
399		case dns_ssumatchtype_selfsub:
400			if (!dns_name_issubdomain(name, signer)) {
401				continue;
402			}
403			break;
404		case dns_ssumatchtype_selfwild:
405			wildcard = dns_fixedname_initname(&fixed);
406			result = dns_name_concatenate(dns_wildcardname, signer,
407						      wildcard, NULL);
408			if (result != ISC_R_SUCCESS) {
409				continue;
410			}
411			if (!dns_name_matcheswildcard(name, wildcard)) {
412				continue;
413			}
414			break;
415		case dns_ssumatchtype_selfkrb5:
416			if (dst_gssapi_identitymatchesrealmkrb5(
417				    signer, name, rule->identity, false))
418			{
419				break;
420			}
421			continue;
422		case dns_ssumatchtype_selfms:
423			if (dst_gssapi_identitymatchesrealmms(
424				    signer, name, rule->identity, false))
425			{
426				break;
427			}
428			continue;
429		case dns_ssumatchtype_selfsubkrb5:
430			if (dst_gssapi_identitymatchesrealmkrb5(
431				    signer, name, rule->identity, true))
432			{
433				break;
434			}
435			continue;
436		case dns_ssumatchtype_selfsubms:
437			if (dst_gssapi_identitymatchesrealmms(
438				    signer, name, rule->identity, true))
439			{
440				break;
441			}
442			continue;
443		case dns_ssumatchtype_subdomainkrb5:
444			if (!dns_name_issubdomain(name, rule->name)) {
445				continue;
446			}
447			if (dst_gssapi_identitymatchesrealmkrb5(
448				    signer, NULL, rule->identity, false))
449			{
450				break;
451			}
452			continue;
453		case dns_ssumatchtype_subdomainms:
454			if (!dns_name_issubdomain(name, rule->name)) {
455				continue;
456			}
457			if (dst_gssapi_identitymatchesrealmms(
458				    signer, NULL, rule->identity, false))
459			{
460				break;
461			}
462			continue;
463		case dns_ssumatchtype_tcpself:
464			tcpself = dns_fixedname_initname(&fixed);
465			reverse_from_address(tcpself, addr);
466			if (dns_name_iswildcard(rule->identity)) {
467				if (!dns_name_matcheswildcard(tcpself,
468							      rule->identity))
469				{
470					continue;
471				}
472			} else {
473				if (!dns_name_equal(tcpself, rule->identity)) {
474					continue;
475				}
476			}
477			if (!dns_name_equal(tcpself, name)) {
478				continue;
479			}
480			break;
481		case dns_ssumatchtype_6to4self:
482			stfself = dns_fixedname_initname(&fixed);
483			stf_from_address(stfself, addr);
484			if (dns_name_iswildcard(rule->identity)) {
485				if (!dns_name_matcheswildcard(stfself,
486							      rule->identity))
487				{
488					continue;
489				}
490			} else {
491				if (!dns_name_equal(stfself, rule->identity)) {
492					continue;
493				}
494			}
495			if (!dns_name_equal(stfself, name)) {
496				continue;
497			}
498			break;
499		case dns_ssumatchtype_external:
500			if (!dns_ssu_external_match(rule->identity, signer,
501						    name, addr, type, key,
502						    table->mctx))
503			{
504				continue;
505			}
506			break;
507		case dns_ssumatchtype_dlz:
508			if (!dns_dlz_ssumatch(table->dlzdatabase, signer, name,
509					      addr, type, key))
510			{
511				continue;
512			}
513			break;
514		}
515
516		if (rule->ntypes == 0) {
517			/*
518			 * If this is a DLZ rule, then the DLZ ssu
519			 * checks will have already checked
520			 * the type.
521			 */
522			if (rule->matchtype != dns_ssumatchtype_dlz &&
523			    !isusertype(type))
524			{
525				continue;
526			}
527		} else {
528			for (i = 0; i < rule->ntypes; i++) {
529				if (rule->types[i] == dns_rdatatype_any ||
530				    rule->types[i] == type)
531				{
532					break;
533				}
534			}
535			if (i == rule->ntypes) {
536				continue;
537			}
538		}
539		return (rule->grant);
540	}
541
542	return (false);
543}
544
545bool
546dns_ssurule_isgrant(const dns_ssurule_t *rule) {
547	REQUIRE(VALID_SSURULE(rule));
548	return (rule->grant);
549}
550
551dns_name_t *
552dns_ssurule_identity(const dns_ssurule_t *rule) {
553	REQUIRE(VALID_SSURULE(rule));
554	return (rule->identity);
555}
556
557unsigned int
558dns_ssurule_matchtype(const dns_ssurule_t *rule) {
559	REQUIRE(VALID_SSURULE(rule));
560	return (rule->matchtype);
561}
562
563dns_name_t *
564dns_ssurule_name(const dns_ssurule_t *rule) {
565	REQUIRE(VALID_SSURULE(rule));
566	return (rule->name);
567}
568
569unsigned int
570dns_ssurule_types(const dns_ssurule_t *rule, dns_rdatatype_t **types) {
571	REQUIRE(VALID_SSURULE(rule));
572	REQUIRE(types != NULL && *types != NULL);
573	*types = rule->types;
574	return (rule->ntypes);
575}
576
577isc_result_t
578dns_ssutable_firstrule(const dns_ssutable_t *table, dns_ssurule_t **rule) {
579	REQUIRE(VALID_SSUTABLE(table));
580	REQUIRE(rule != NULL && *rule == NULL);
581	*rule = ISC_LIST_HEAD(table->rules);
582	return (*rule != NULL ? ISC_R_SUCCESS : ISC_R_NOMORE);
583}
584
585isc_result_t
586dns_ssutable_nextrule(dns_ssurule_t *rule, dns_ssurule_t **nextrule) {
587	REQUIRE(VALID_SSURULE(rule));
588	REQUIRE(nextrule != NULL && *nextrule == NULL);
589	*nextrule = ISC_LIST_NEXT(rule, link);
590	return (*nextrule != NULL ? ISC_R_SUCCESS : ISC_R_NOMORE);
591}
592
593/*
594 * Create a specialised SSU table that points at an external DLZ database
595 */
596isc_result_t
597dns_ssutable_createdlz(isc_mem_t *mctx, dns_ssutable_t **tablep,
598		       dns_dlzdb_t *dlzdatabase) {
599	isc_result_t result;
600	dns_ssurule_t *rule;
601	dns_ssutable_t *table = NULL;
602
603	REQUIRE(tablep != NULL && *tablep == NULL);
604
605	result = dns_ssutable_create(mctx, &table);
606	if (result != ISC_R_SUCCESS) {
607		return (result);
608	}
609
610	table->dlzdatabase = dlzdatabase;
611
612	rule = isc_mem_get(table->mctx, sizeof(dns_ssurule_t));
613
614	rule->identity = NULL;
615	rule->name = NULL;
616	rule->types = NULL;
617	rule->grant = true;
618	rule->matchtype = dns_ssumatchtype_dlz;
619	rule->ntypes = 0;
620	rule->types = NULL;
621	rule->magic = SSURULEMAGIC;
622
623	ISC_LIST_INITANDAPPEND(table->rules, rule, link);
624	*tablep = table;
625	return (ISC_R_SUCCESS);
626}
627
628isc_result_t
629dns_ssu_mtypefromstring(const char *str, dns_ssumatchtype_t *mtype) {
630	REQUIRE(str != NULL);
631	REQUIRE(mtype != NULL);
632
633	if (strcasecmp(str, "name") == 0) {
634		*mtype = dns_ssumatchtype_name;
635	} else if (strcasecmp(str, "subdomain") == 0) {
636		*mtype = dns_ssumatchtype_subdomain;
637	} else if (strcasecmp(str, "wildcard") == 0) {
638		*mtype = dns_ssumatchtype_wildcard;
639	} else if (strcasecmp(str, "self") == 0) {
640		*mtype = dns_ssumatchtype_self;
641	} else if (strcasecmp(str, "selfsub") == 0) {
642		*mtype = dns_ssumatchtype_selfsub;
643	} else if (strcasecmp(str, "selfwild") == 0) {
644		*mtype = dns_ssumatchtype_selfwild;
645	} else if (strcasecmp(str, "ms-self") == 0) {
646		*mtype = dns_ssumatchtype_selfms;
647	} else if (strcasecmp(str, "ms-selfsub") == 0) {
648		*mtype = dns_ssumatchtype_selfsubms;
649	} else if (strcasecmp(str, "krb5-self") == 0) {
650		*mtype = dns_ssumatchtype_selfkrb5;
651	} else if (strcasecmp(str, "krb5-selfsub") == 0) {
652		*mtype = dns_ssumatchtype_selfsubkrb5;
653	} else if (strcasecmp(str, "ms-subdomain") == 0) {
654		*mtype = dns_ssumatchtype_subdomainms;
655	} else if (strcasecmp(str, "krb5-subdomain") == 0) {
656		*mtype = dns_ssumatchtype_subdomainkrb5;
657	} else if (strcasecmp(str, "tcp-self") == 0) {
658		*mtype = dns_ssumatchtype_tcpself;
659	} else if (strcasecmp(str, "6to4-self") == 0) {
660		*mtype = dns_ssumatchtype_6to4self;
661	} else if (strcasecmp(str, "zonesub") == 0) {
662		*mtype = dns_ssumatchtype_subdomain;
663	} else if (strcasecmp(str, "external") == 0) {
664		*mtype = dns_ssumatchtype_external;
665	} else {
666		return (ISC_R_NOTFOUND);
667	}
668	return (ISC_R_SUCCESS);
669}
670