1 /*
2 * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
3 *
4 * SPDX-License-Identifier: MPL-2.0
5 *
6 * This Source Code Form is subject to the terms of the Mozilla Public
7 * License, v. 2.0. If a copy of the MPL was not distributed with this
8 * file, you can obtain one at https://mozilla.org/MPL/2.0/.
9 *
10 * See the COPYRIGHT file distributed with this work for additional
11 * information regarding copyright ownership.
12 */
13
14 #include <ctype.h>
15 #include <inttypes.h>
16 #include <stdbool.h>
17 #include <stdlib.h>
18 #include <string.h>
19
20 #include <isc/buffer.h>
21 #include <isc/dir.h>
22 #include <isc/file.h>
23 #include <isc/lex.h>
24 #include <isc/mem.h>
25 #include <isc/once.h>
26 #include <isc/platform.h>
27 #include <isc/print.h>
28 #include <isc/random.h>
29 #include <isc/string.h>
30 #include <isc/time.h>
31 #include <isc/util.h>
32
33 #include <dns/fixedname.h>
34 #include <dns/keyvalues.h>
35 #include <dns/log.h>
36 #include <dns/name.h>
37 #include <dns/rdata.h>
38 #include <dns/rdataclass.h>
39 #include <dns/result.h>
40 #include <dns/types.h>
41
42 #include <dst/gssapi.h>
43 #include <dst/result.h>
44
45 #include "dst_internal.h"
46
47 /*
48 * Solaris8 apparently needs an explicit OID set, and Solaris10 needs
49 * one for anything but Kerberos. Supplying an explicit OID set
50 * doesn't appear to hurt anything in other implementations, so we
51 * always use one. If we're not using our own SPNEGO implementation,
52 * we include SPNEGO's OID.
53 */
54 #ifdef GSSAPI
55 #ifdef WIN32
56 #include <krb5/krb5.h>
57 #else /* ifdef WIN32 */
58 #include ISC_PLATFORM_KRB5HEADER
59 #endif /* ifdef WIN32 */
60
61 #ifndef GSS_KRB5_MECHANISM
62 static unsigned char krb5_mech_oid_bytes[] = { 0x2a, 0x86, 0x48, 0x86, 0xf7,
63 0x12, 0x01, 0x02, 0x02 };
64 static gss_OID_desc __gss_krb5_mechanism_oid_desc = {
65 sizeof(krb5_mech_oid_bytes), krb5_mech_oid_bytes
66 };
67 #define GSS_KRB5_MECHANISM (&__gss_krb5_mechanism_oid_desc)
68 #endif /* ifndef GSS_KRB5_MECHANISM */
69
70 #ifndef GSS_SPNEGO_MECHANISM
71 static unsigned char spnego_mech_oid_bytes[] = { 0x2b, 0x06, 0x01,
72 0x05, 0x05, 0x02 };
73 static gss_OID_desc __gss_spnego_mechanism_oid_desc = {
74 sizeof(spnego_mech_oid_bytes), spnego_mech_oid_bytes
75 };
76 #define GSS_SPNEGO_MECHANISM (&__gss_spnego_mechanism_oid_desc)
77 #endif /* ifndef GSS_SPNEGO_MECHANISM */
78
79 #define REGION_TO_GBUFFER(r, gb) \
80 do { \
81 (gb).length = (r).length; \
82 (gb).value = (r).base; \
83 } while (0)
84
85 #define GBUFFER_TO_REGION(gb, r) \
86 do { \
87 (r).length = (unsigned int)(gb).length; \
88 (r).base = (gb).value; \
89 } while (0)
90
91 #define RETERR(x) \
92 do { \
93 result = (x); \
94 if (result != ISC_R_SUCCESS) \
95 goto out; \
96 } while (0)
97
98 static inline void
name_to_gbuffer(const dns_name_t * name,isc_buffer_t * buffer,gss_buffer_desc * gbuffer)99 name_to_gbuffer(const dns_name_t *name, isc_buffer_t *buffer,
100 gss_buffer_desc *gbuffer) {
101 dns_name_t tname;
102 const dns_name_t *namep;
103 isc_region_t r;
104 isc_result_t result;
105
106 if (!dns_name_isabsolute(name)) {
107 namep = name;
108 } else {
109 unsigned int labels;
110 dns_name_init(&tname, NULL);
111 labels = dns_name_countlabels(name);
112 dns_name_getlabelsequence(name, 0, labels - 1, &tname);
113 namep = &tname;
114 }
115
116 result = dns_name_toprincipal(namep, buffer);
117 RUNTIME_CHECK(result == ISC_R_SUCCESS);
118 isc_buffer_putuint8(buffer, 0);
119 isc_buffer_usedregion(buffer, &r);
120 REGION_TO_GBUFFER(r, *gbuffer);
121 }
122
123 static void
log_cred(const dns_gss_cred_id_t cred)124 log_cred(const dns_gss_cred_id_t cred) {
125 OM_uint32 gret, minor, lifetime;
126 gss_name_t gname;
127 gss_buffer_desc gbuffer;
128 gss_cred_usage_t usage;
129 const char *usage_text;
130 char buf[1024];
131
132 gret = gss_inquire_cred(&minor, (gss_cred_id_t)cred, &gname, &lifetime,
133 &usage, NULL);
134 if (gret != GSS_S_COMPLETE) {
135 gss_log(3, "failed gss_inquire_cred: %s",
136 gss_error_tostring(gret, minor, buf, sizeof(buf)));
137 return;
138 }
139
140 gret = gss_display_name(&minor, gname, &gbuffer, NULL);
141 if (gret != GSS_S_COMPLETE) {
142 gss_log(3, "failed gss_display_name: %s",
143 gss_error_tostring(gret, minor, buf, sizeof(buf)));
144 } else {
145 switch (usage) {
146 case GSS_C_BOTH:
147 usage_text = "GSS_C_BOTH";
148 break;
149 case GSS_C_INITIATE:
150 usage_text = "GSS_C_INITIATE";
151 break;
152 case GSS_C_ACCEPT:
153 usage_text = "GSS_C_ACCEPT";
154 break;
155 default:
156 usage_text = "???";
157 }
158 gss_log(3, "gss cred: \"%s\", %s, %lu", (char *)gbuffer.value,
159 usage_text, (unsigned long)lifetime);
160 }
161
162 if (gret == GSS_S_COMPLETE) {
163 if (gbuffer.length != 0U) {
164 gret = gss_release_buffer(&minor, &gbuffer);
165 if (gret != GSS_S_COMPLETE) {
166 gss_log(3, "failed gss_release_buffer: %s",
167 gss_error_tostring(gret, minor, buf,
168 sizeof(buf)));
169 }
170 }
171 }
172
173 gret = gss_release_name(&minor, &gname);
174 if (gret != GSS_S_COMPLETE) {
175 gss_log(3, "failed gss_release_name: %s",
176 gss_error_tostring(gret, minor, buf, sizeof(buf)));
177 }
178 }
179
180 /*
181 * check for the most common configuration errors.
182 *
183 * The errors checked for are:
184 * - tkey-gssapi-credential doesn't start with DNS/
185 * - the default realm in /etc/krb5.conf and the
186 * tkey-gssapi-credential bind config option don't match
187 *
188 * Note that if tkey-gssapi-keytab is set then these configure checks
189 * are not performed, and runtime errors from gssapi are used instead
190 */
191 static void
check_config(const char * gss_name)192 check_config(const char *gss_name) {
193 const char *p;
194 krb5_context krb5_ctx;
195 char *krb5_realm_name = NULL;
196
197 if (strncasecmp(gss_name, "DNS/", 4) != 0) {
198 gss_log(ISC_LOG_ERROR,
199 "tkey-gssapi-credential (%s) "
200 "should start with 'DNS/'",
201 gss_name);
202 return;
203 }
204
205 if (krb5_init_context(&krb5_ctx) != 0) {
206 gss_log(ISC_LOG_ERROR, "Unable to initialise krb5 context");
207 return;
208 }
209 if (krb5_get_default_realm(krb5_ctx, &krb5_realm_name) != 0) {
210 gss_log(ISC_LOG_ERROR, "Unable to get krb5 default realm");
211 krb5_free_context(krb5_ctx);
212 return;
213 }
214 p = strchr(gss_name, '@');
215 if (p == NULL) {
216 gss_log(ISC_LOG_ERROR,
217 "badly formatted "
218 "tkey-gssapi-credentials (%s)",
219 gss_name);
220 krb5_free_context(krb5_ctx);
221 return;
222 }
223 if (strcasecmp(p + 1, krb5_realm_name) != 0) {
224 gss_log(ISC_LOG_ERROR,
225 "default realm from krb5.conf (%s) "
226 "does not match tkey-gssapi-credential (%s)",
227 krb5_realm_name, gss_name);
228 krb5_free_context(krb5_ctx);
229 return;
230 }
231 krb5_free_context(krb5_ctx);
232 }
233
234 static OM_uint32
mech_oid_set_create(OM_uint32 * minor,gss_OID_set * mech_oid_set)235 mech_oid_set_create(OM_uint32 *minor, gss_OID_set *mech_oid_set) {
236 OM_uint32 gret;
237
238 gret = gss_create_empty_oid_set(minor, mech_oid_set);
239 if (gret != GSS_S_COMPLETE) {
240 return (gret);
241 }
242
243 gret = gss_add_oid_set_member(minor, GSS_KRB5_MECHANISM, mech_oid_set);
244 if (gret != GSS_S_COMPLETE) {
245 goto release;
246 }
247
248 gret = gss_add_oid_set_member(minor, GSS_SPNEGO_MECHANISM,
249 mech_oid_set);
250 if (gret != GSS_S_COMPLETE) {
251 goto release;
252 }
253
254 release:
255 REQUIRE(gss_release_oid_set(minor, mech_oid_set) == GSS_S_COMPLETE);
256
257 return (gret);
258 }
259
260 static void
mech_oid_set_release(gss_OID_set * mech_oid_set)261 mech_oid_set_release(gss_OID_set *mech_oid_set) {
262 OM_uint32 minor;
263
264 REQUIRE(gss_release_oid_set(&minor, mech_oid_set) == GSS_S_COMPLETE);
265 }
266
267 isc_result_t
dst_gssapi_acquirecred(const dns_name_t * name,bool initiate,dns_gss_cred_id_t * cred)268 dst_gssapi_acquirecred(const dns_name_t *name, bool initiate,
269 dns_gss_cred_id_t *cred) {
270 isc_result_t result;
271 isc_buffer_t namebuf;
272 gss_name_t gname;
273 gss_buffer_desc gnamebuf;
274 unsigned char array[DNS_NAME_MAXTEXT + 1];
275 OM_uint32 gret, minor;
276 OM_uint32 lifetime;
277 gss_cred_usage_t usage;
278 char buf[1024];
279 gss_OID_set mech_oid_set = NULL;
280
281 REQUIRE(cred != NULL && *cred == NULL);
282
283 /*
284 * XXXSRA In theory we could use GSS_C_NT_HOSTBASED_SERVICE
285 * here when we're in the acceptor role, which would let us
286 * default the hostname and use a compiled in default service
287 * name of "DNS", giving one less thing to configure in
288 * named.conf. Unfortunately, this creates a circular
289 * dependency due to DNS-based realm lookup in at least one
290 * GSSAPI implementation (Heimdal). Oh well.
291 */
292 if (name != NULL) {
293 isc_buffer_init(&namebuf, array, sizeof(array));
294 name_to_gbuffer(name, &namebuf, &gnamebuf);
295 gret = gss_import_name(&minor, &gnamebuf, GSS_C_NO_OID, &gname);
296 if (gret != GSS_S_COMPLETE) {
297 check_config((char *)array);
298
299 gss_log(3, "failed gss_import_name: %s",
300 gss_error_tostring(gret, minor, buf,
301 sizeof(buf)));
302 return (ISC_R_FAILURE);
303 }
304 } else {
305 gname = NULL;
306 }
307
308 /* Get the credentials. */
309 if (gname != NULL) {
310 gss_log(3, "acquiring credentials for %s",
311 (char *)gnamebuf.value);
312 } else {
313 /* XXXDCL does this even make any sense? */
314 gss_log(3, "acquiring credentials for ?");
315 }
316
317 if (initiate) {
318 usage = GSS_C_INITIATE;
319 } else {
320 usage = GSS_C_ACCEPT;
321 }
322
323 gret = mech_oid_set_create(&minor, &mech_oid_set);
324 if (gret != GSS_S_COMPLETE) {
325 gss_log(3, "failed to create OID_set: %s",
326 gss_error_tostring(gret, minor, buf, sizeof(buf)));
327 return (ISC_R_FAILURE);
328 }
329
330 gret = gss_acquire_cred(&minor, gname, GSS_C_INDEFINITE, mech_oid_set,
331 usage, (gss_cred_id_t *)cred, NULL, &lifetime);
332
333 if (gret != GSS_S_COMPLETE) {
334 gss_log(3, "failed to acquire %s credentials for %s: %s",
335 initiate ? "initiate" : "accept",
336 (gname != NULL) ? (char *)gnamebuf.value : "?",
337 gss_error_tostring(gret, minor, buf, sizeof(buf)));
338 if (gname != NULL) {
339 check_config((char *)array);
340 }
341 result = ISC_R_FAILURE;
342 goto cleanup;
343 }
344
345 gss_log(4, "acquired %s credentials for %s",
346 initiate ? "initiate" : "accept",
347 (gname != NULL) ? (char *)gnamebuf.value : "?");
348
349 log_cred(*cred);
350 result = ISC_R_SUCCESS;
351
352 cleanup:
353 mech_oid_set_release(&mech_oid_set);
354
355 if (gname != NULL) {
356 gret = gss_release_name(&minor, &gname);
357 if (gret != GSS_S_COMPLETE) {
358 gss_log(3, "failed gss_release_name: %s",
359 gss_error_tostring(gret, minor, buf,
360 sizeof(buf)));
361 }
362 }
363
364 return (result);
365 }
366
367 bool
dst_gssapi_identitymatchesrealmkrb5(const dns_name_t * signer,const dns_name_t * name,const dns_name_t * realm,bool subdomain)368 dst_gssapi_identitymatchesrealmkrb5(const dns_name_t *signer,
369 const dns_name_t *name,
370 const dns_name_t *realm, bool subdomain) {
371 char sbuf[DNS_NAME_FORMATSIZE];
372 char rbuf[DNS_NAME_FORMATSIZE];
373 char *sname;
374 char *rname;
375 isc_buffer_t buffer;
376 isc_result_t result;
377
378 /*
379 * It is far, far easier to write the names we are looking at into
380 * a string, and do string operations on them.
381 */
382 isc_buffer_init(&buffer, sbuf, sizeof(sbuf));
383 result = dns_name_toprincipal(signer, &buffer);
384 RUNTIME_CHECK(result == ISC_R_SUCCESS);
385 isc_buffer_putuint8(&buffer, 0);
386 dns_name_format(realm, rbuf, sizeof(rbuf));
387
388 /*
389 * Find the realm portion. This is the part after the @. If it
390 * does not exist, we don't have something we like, so we fail our
391 * compare.
392 */
393 rname = strchr(sbuf, '@');
394 if (rname == NULL) {
395 return (false);
396 }
397 *rname = '\0';
398 rname++;
399
400 if (strcmp(rname, rbuf) != 0) {
401 return (false);
402 }
403
404 /*
405 * Find the host portion of the signer's name. We do this by
406 * searching for the first / character. We then check to make
407 * certain the instance name is "host"
408 *
409 * This will work for
410 * host/example.com@EXAMPLE.COM
411 */
412 sname = strchr(sbuf, '/');
413 if (sname == NULL) {
414 return (false);
415 }
416 *sname = '\0';
417 sname++;
418 if (strcmp(sbuf, "host") != 0) {
419 return (false);
420 }
421
422 /*
423 * If name is non NULL check that it matches against the
424 * machine name as expected.
425 */
426 if (name != NULL) {
427 dns_fixedname_t fixed;
428 dns_name_t *machine;
429
430 machine = dns_fixedname_initname(&fixed);
431 result = dns_name_fromstring(machine, sname, 0, NULL);
432 if (result != ISC_R_SUCCESS) {
433 return (false);
434 }
435 if (subdomain) {
436 return (dns_name_issubdomain(name, machine));
437 }
438 return (dns_name_equal(name, machine));
439 }
440
441 return (true);
442 }
443
444 bool
dst_gssapi_identitymatchesrealmms(const dns_name_t * signer,const dns_name_t * name,const dns_name_t * realm,bool subdomain)445 dst_gssapi_identitymatchesrealmms(const dns_name_t *signer,
446 const dns_name_t *name,
447 const dns_name_t *realm, bool subdomain) {
448 char sbuf[DNS_NAME_FORMATSIZE];
449 char rbuf[DNS_NAME_FORMATSIZE];
450 char *sname;
451 char *rname;
452 isc_buffer_t buffer;
453 isc_result_t result;
454
455 /*
456 * It is far, far easier to write the names we are looking at into
457 * a string, and do string operations on them.
458 */
459 isc_buffer_init(&buffer, sbuf, sizeof(sbuf));
460 result = dns_name_toprincipal(signer, &buffer);
461 RUNTIME_CHECK(result == ISC_R_SUCCESS);
462 isc_buffer_putuint8(&buffer, 0);
463 dns_name_format(realm, rbuf, sizeof(rbuf));
464
465 /*
466 * Find the realm portion. This is the part after the @. If it
467 * does not exist, we don't have something we like, so we fail our
468 * compare.
469 */
470 rname = strchr(sbuf, '@');
471 if (rname == NULL) {
472 return (false);
473 }
474 sname = strchr(sbuf, '$');
475 if (sname == NULL) {
476 return (false);
477 }
478
479 /*
480 * Verify that the $ and @ follow one another.
481 */
482 if (rname - sname != 1) {
483 return (false);
484 }
485
486 /*
487 * Find the host portion of the signer's name. Zero out the $ so
488 * it terminates the signer's name, and skip past the @ for
489 * the realm.
490 *
491 * All service principals in Microsoft format seem to be in
492 * machinename$@EXAMPLE.COM
493 * format.
494 */
495 rname++;
496 *sname = '\0';
497
498 if (strcmp(rname, rbuf) != 0) {
499 return (false);
500 }
501
502 /*
503 * Now, we check that the realm matches (case sensitive) and that
504 * 'name' matches against 'machinename' qualified with 'realm'.
505 */
506 if (name != NULL) {
507 dns_fixedname_t fixed;
508 dns_name_t *machine;
509
510 machine = dns_fixedname_initname(&fixed);
511 result = dns_name_fromstring2(machine, sbuf, realm, 0, NULL);
512 if (result != ISC_R_SUCCESS) {
513 return (false);
514 }
515 if (subdomain) {
516 return (dns_name_issubdomain(name, machine));
517 }
518 return (dns_name_equal(name, machine));
519 }
520
521 return (true);
522 }
523
524 isc_result_t
dst_gssapi_releasecred(dns_gss_cred_id_t * cred)525 dst_gssapi_releasecred(dns_gss_cred_id_t *cred) {
526 OM_uint32 gret, minor;
527 char buf[1024];
528
529 REQUIRE(cred != NULL && *cred != NULL);
530
531 gret = gss_release_cred(&minor, (gss_cred_id_t *)cred);
532 if (gret != GSS_S_COMPLETE) {
533 /* Log the error, but still free the credential's memory */
534 gss_log(3, "failed releasing credential: %s",
535 gss_error_tostring(gret, minor, buf, sizeof(buf)));
536 }
537 *cred = NULL;
538
539 return (ISC_R_SUCCESS);
540 }
541
542 /*
543 * Format a gssapi error message info into a char ** on the given memory
544 * context. This is used to return gssapi error messages back up the
545 * call chain for reporting to the user.
546 */
547 static void
gss_err_message(isc_mem_t * mctx,uint32_t major,uint32_t minor,char ** err_message)548 gss_err_message(isc_mem_t *mctx, uint32_t major, uint32_t minor,
549 char **err_message) {
550 char buf[1024];
551 char *estr;
552
553 if (err_message == NULL || mctx == NULL) {
554 /* the caller doesn't want any error messages */
555 return;
556 }
557
558 estr = gss_error_tostring(major, minor, buf, sizeof(buf));
559 if (estr != NULL) {
560 (*err_message) = isc_mem_strdup(mctx, estr);
561 }
562 }
563
564 isc_result_t
dst_gssapi_initctx(const dns_name_t * name,isc_buffer_t * intoken,isc_buffer_t * outtoken,dns_gss_ctx_id_t * gssctx,isc_mem_t * mctx,char ** err_message)565 dst_gssapi_initctx(const dns_name_t *name, isc_buffer_t *intoken,
566 isc_buffer_t *outtoken, dns_gss_ctx_id_t *gssctx,
567 isc_mem_t *mctx, char **err_message) {
568 isc_region_t r;
569 isc_buffer_t namebuf;
570 gss_name_t gname;
571 OM_uint32 gret, minor, ret_flags, flags;
572 gss_buffer_desc gintoken, *gintokenp, gouttoken = GSS_C_EMPTY_BUFFER;
573 isc_result_t result;
574 gss_buffer_desc gnamebuf;
575 unsigned char array[DNS_NAME_MAXTEXT + 1];
576
577 /* Client must pass us a valid gss_ctx_id_t here */
578 REQUIRE(gssctx != NULL);
579 REQUIRE(mctx != NULL);
580
581 isc_buffer_init(&namebuf, array, sizeof(array));
582 name_to_gbuffer(name, &namebuf, &gnamebuf);
583
584 /* Get the name as a GSS name */
585 gret = gss_import_name(&minor, &gnamebuf, GSS_C_NO_OID, &gname);
586 if (gret != GSS_S_COMPLETE) {
587 gss_err_message(mctx, gret, minor, err_message);
588 result = ISC_R_FAILURE;
589 goto out;
590 }
591
592 if (intoken != NULL) {
593 /* Don't call gss_release_buffer for gintoken! */
594 REGION_TO_GBUFFER(*intoken, gintoken);
595 gintokenp = &gintoken;
596 } else {
597 gintokenp = NULL;
598 }
599
600 /*
601 * Note that we don't set GSS_C_SEQUENCE_FLAG as Windows DNS
602 * servers don't like it.
603 */
604 flags = GSS_C_REPLAY_FLAG | GSS_C_MUTUAL_FLAG | GSS_C_INTEG_FLAG;
605
606 gret = gss_init_sec_context(
607 &minor, GSS_C_NO_CREDENTIAL, (gss_ctx_id_t *)gssctx, gname,
608 GSS_SPNEGO_MECHANISM, flags, 0, NULL, gintokenp, NULL,
609 &gouttoken, &ret_flags, NULL);
610
611 if (gret != GSS_S_COMPLETE && gret != GSS_S_CONTINUE_NEEDED) {
612 gss_err_message(mctx, gret, minor, err_message);
613 if (err_message != NULL && *err_message != NULL) {
614 gss_log(3, "Failure initiating security context: %s",
615 *err_message);
616 } else {
617 gss_log(3, "Failure initiating security context");
618 }
619
620 result = ISC_R_FAILURE;
621 goto out;
622 }
623
624 /*
625 * XXXSRA Not handled yet: RFC 3645 3.1.1: check ret_flags
626 * MUTUAL and INTEG flags, fail if either not set.
627 */
628
629 /*
630 * RFC 2744 states the a valid output token has a non-zero length.
631 */
632 if (gouttoken.length != 0U) {
633 GBUFFER_TO_REGION(gouttoken, r);
634 RETERR(isc_buffer_copyregion(outtoken, &r));
635 }
636
637 if (gret == GSS_S_COMPLETE) {
638 result = ISC_R_SUCCESS;
639 } else {
640 result = DNS_R_CONTINUE;
641 }
642
643 out:
644 if (gouttoken.length != 0U) {
645 (void)gss_release_buffer(&minor, &gouttoken);
646 }
647 (void)gss_release_name(&minor, &gname);
648 return (result);
649 }
650
651 isc_result_t
dst_gssapi_acceptctx(dns_gss_cred_id_t cred,const char * gssapi_keytab,isc_region_t * intoken,isc_buffer_t ** outtoken,dns_gss_ctx_id_t * ctxout,dns_name_t * principal,isc_mem_t * mctx)652 dst_gssapi_acceptctx(dns_gss_cred_id_t cred, const char *gssapi_keytab,
653 isc_region_t *intoken, isc_buffer_t **outtoken,
654 dns_gss_ctx_id_t *ctxout, dns_name_t *principal,
655 isc_mem_t *mctx) {
656 isc_region_t r;
657 isc_buffer_t namebuf;
658 gss_buffer_desc gnamebuf = GSS_C_EMPTY_BUFFER, gintoken,
659 gouttoken = GSS_C_EMPTY_BUFFER;
660 OM_uint32 gret, minor;
661 gss_ctx_id_t context = GSS_C_NO_CONTEXT;
662 gss_name_t gname = NULL;
663 isc_result_t result;
664 char buf[1024];
665
666 REQUIRE(outtoken != NULL && *outtoken == NULL);
667
668 REGION_TO_GBUFFER(*intoken, gintoken);
669
670 if (*ctxout == NULL) {
671 context = GSS_C_NO_CONTEXT;
672 } else {
673 context = *ctxout;
674 }
675
676 if (gssapi_keytab != NULL) {
677 #if defined(ISC_PLATFORM_GSSAPI_KRB5_HEADER) || defined(WIN32)
678 gret = gsskrb5_register_acceptor_identity(gssapi_keytab);
679 if (gret != GSS_S_COMPLETE) {
680 gss_log(3,
681 "failed "
682 "gsskrb5_register_acceptor_identity(%s): %s",
683 gssapi_keytab,
684 gss_error_tostring(gret, 0, buf, sizeof(buf)));
685 return (DNS_R_INVALIDTKEY);
686 }
687 #else /* if defined(ISC_PLATFORM_GSSAPI_KRB5_HEADER) || defined(WIN32) */
688 /*
689 * Minimize memory leakage by only setting KRB5_KTNAME
690 * if it needs to change.
691 */
692 const char *old = getenv("KRB5_KTNAME");
693 if (old == NULL || strcmp(old, gssapi_keytab) != 0) {
694 size_t size;
695 char *kt;
696
697 size = strlen(gssapi_keytab) + 13;
698 kt = malloc(size);
699 if (kt == NULL) {
700 return (ISC_R_NOMEMORY);
701 }
702 snprintf(kt, size, "KRB5_KTNAME=%s", gssapi_keytab);
703 if (putenv(kt) != 0) {
704 return (ISC_R_NOMEMORY);
705 }
706 }
707 #endif /* if defined(ISC_PLATFORM_GSSAPI_KRB5_HEADER) || defined(WIN32) */
708 }
709
710 log_cred(cred);
711
712 gret = gss_accept_sec_context(&minor, &context, cred, &gintoken,
713 GSS_C_NO_CHANNEL_BINDINGS, &gname, NULL,
714 &gouttoken, NULL, NULL, NULL);
715
716 result = ISC_R_FAILURE;
717
718 switch (gret) {
719 case GSS_S_COMPLETE:
720 case GSS_S_CONTINUE_NEEDED:
721 break;
722 case GSS_S_DEFECTIVE_TOKEN:
723 case GSS_S_DEFECTIVE_CREDENTIAL:
724 case GSS_S_BAD_SIG:
725 case GSS_S_DUPLICATE_TOKEN:
726 case GSS_S_OLD_TOKEN:
727 case GSS_S_NO_CRED:
728 case GSS_S_CREDENTIALS_EXPIRED:
729 case GSS_S_BAD_BINDINGS:
730 case GSS_S_NO_CONTEXT:
731 case GSS_S_BAD_MECH:
732 case GSS_S_FAILURE:
733 result = DNS_R_INVALIDTKEY;
734 /* fall through */
735 default:
736 gss_log(3, "failed gss_accept_sec_context: %s",
737 gss_error_tostring(gret, minor, buf, sizeof(buf)));
738 if (gouttoken.length > 0U) {
739 (void)gss_release_buffer(&minor, &gouttoken);
740 }
741 return (result);
742 }
743
744 if (gouttoken.length > 0U) {
745 isc_buffer_allocate(mctx, outtoken,
746 (unsigned int)gouttoken.length);
747 GBUFFER_TO_REGION(gouttoken, r);
748 RETERR(isc_buffer_copyregion(*outtoken, &r));
749 (void)gss_release_buffer(&minor, &gouttoken);
750 }
751
752 if (gret == GSS_S_COMPLETE) {
753 gret = gss_display_name(&minor, gname, &gnamebuf, NULL);
754 if (gret != GSS_S_COMPLETE) {
755 gss_log(3, "failed gss_display_name: %s",
756 gss_error_tostring(gret, minor, buf,
757 sizeof(buf)));
758 RETERR(ISC_R_FAILURE);
759 }
760
761 /*
762 * Compensate for a bug in Solaris8's implementation
763 * of gss_display_name(). Should be harmless in any
764 * case, since principal names really should not
765 * contain null characters.
766 */
767 if (gnamebuf.length > 0U &&
768 ((char *)gnamebuf.value)[gnamebuf.length - 1] == '\0')
769 {
770 gnamebuf.length--;
771 }
772
773 gss_log(3, "gss-api source name (accept) is %.*s",
774 (int)gnamebuf.length, (char *)gnamebuf.value);
775
776 GBUFFER_TO_REGION(gnamebuf, r);
777 isc_buffer_init(&namebuf, r.base, r.length);
778 isc_buffer_add(&namebuf, r.length);
779
780 RETERR(dns_name_fromtext(principal, &namebuf, dns_rootname, 0,
781 NULL));
782
783 if (gnamebuf.length != 0U) {
784 gret = gss_release_buffer(&minor, &gnamebuf);
785 if (gret != GSS_S_COMPLETE) {
786 gss_log(3, "failed gss_release_buffer: %s",
787 gss_error_tostring(gret, minor, buf,
788 sizeof(buf)));
789 }
790 }
791 } else {
792 result = DNS_R_CONTINUE;
793 }
794
795 *ctxout = context;
796
797 out:
798 if (gname != NULL) {
799 gret = gss_release_name(&minor, &gname);
800 if (gret != GSS_S_COMPLETE) {
801 gss_log(3, "failed gss_release_name: %s",
802 gss_error_tostring(gret, minor, buf,
803 sizeof(buf)));
804 }
805 }
806
807 return (result);
808 }
809
810 isc_result_t
dst_gssapi_deletectx(isc_mem_t * mctx,dns_gss_ctx_id_t * gssctx)811 dst_gssapi_deletectx(isc_mem_t *mctx, dns_gss_ctx_id_t *gssctx) {
812 OM_uint32 gret, minor;
813 char buf[1024];
814
815 UNUSED(mctx);
816
817 REQUIRE(gssctx != NULL && *gssctx != NULL);
818
819 /* Delete the context from the GSS provider */
820 gret = gss_delete_sec_context(&minor, (gss_ctx_id_t *)gssctx,
821 GSS_C_NO_BUFFER);
822 if (gret != GSS_S_COMPLETE) {
823 /* Log the error, but still free the context's memory */
824 gss_log(3, "Failure deleting security context %s",
825 gss_error_tostring(gret, minor, buf, sizeof(buf)));
826 }
827 return (ISC_R_SUCCESS);
828 }
829
830 char *
gss_error_tostring(uint32_t major,uint32_t minor,char * buf,size_t buflen)831 gss_error_tostring(uint32_t major, uint32_t minor, char *buf, size_t buflen) {
832 gss_buffer_desc msg_minor = GSS_C_EMPTY_BUFFER,
833 msg_major = GSS_C_EMPTY_BUFFER;
834 OM_uint32 msg_ctx, minor_stat;
835
836 /* Handle major status */
837 msg_ctx = 0;
838 (void)gss_display_status(&minor_stat, major, GSS_C_GSS_CODE,
839 GSS_C_NULL_OID, &msg_ctx, &msg_major);
840
841 /* Handle minor status */
842 msg_ctx = 0;
843 (void)gss_display_status(&minor_stat, minor, GSS_C_MECH_CODE,
844 GSS_C_NULL_OID, &msg_ctx, &msg_minor);
845
846 snprintf(buf, buflen, "GSSAPI error: Major = %s, Minor = %s.",
847 (char *)msg_major.value, (char *)msg_minor.value);
848
849 if (msg_major.length != 0U) {
850 (void)gss_release_buffer(&minor_stat, &msg_major);
851 }
852 if (msg_minor.length != 0U) {
853 (void)gss_release_buffer(&minor_stat, &msg_minor);
854 }
855 return (buf);
856 }
857
858 #else /* ifdef GSSAPI */
859
860 isc_result_t
dst_gssapi_acquirecred(const dns_name_t * name,bool initiate,dns_gss_cred_id_t * cred)861 dst_gssapi_acquirecred(const dns_name_t *name, bool initiate,
862 dns_gss_cred_id_t *cred) {
863 REQUIRE(cred != NULL && *cred == NULL);
864
865 UNUSED(name);
866 UNUSED(initiate);
867 UNUSED(cred);
868
869 return (ISC_R_NOTIMPLEMENTED);
870 }
871
872 bool
dst_gssapi_identitymatchesrealmkrb5(const dns_name_t * signer,const dns_name_t * name,const dns_name_t * realm,bool subdomain)873 dst_gssapi_identitymatchesrealmkrb5(const dns_name_t *signer,
874 const dns_name_t *name,
875 const dns_name_t *realm, bool subdomain) {
876 UNUSED(signer);
877 UNUSED(name);
878 UNUSED(realm);
879 UNUSED(subdomain);
880 return (false);
881 }
882
883 bool
dst_gssapi_identitymatchesrealmms(const dns_name_t * signer,const dns_name_t * name,const dns_name_t * realm,bool subdomain)884 dst_gssapi_identitymatchesrealmms(const dns_name_t *signer,
885 const dns_name_t *name,
886 const dns_name_t *realm, bool subdomain) {
887 UNUSED(signer);
888 UNUSED(name);
889 UNUSED(realm);
890 UNUSED(subdomain);
891 return (false);
892 }
893
894 isc_result_t
dst_gssapi_releasecred(dns_gss_cred_id_t * cred)895 dst_gssapi_releasecred(dns_gss_cred_id_t *cred) {
896 UNUSED(cred);
897
898 return (ISC_R_NOTIMPLEMENTED);
899 }
900
901 isc_result_t
dst_gssapi_initctx(const dns_name_t * name,isc_buffer_t * intoken,isc_buffer_t * outtoken,dns_gss_ctx_id_t * gssctx,isc_mem_t * mctx,char ** err_message)902 dst_gssapi_initctx(const dns_name_t *name, isc_buffer_t *intoken,
903 isc_buffer_t *outtoken, dns_gss_ctx_id_t *gssctx,
904 isc_mem_t *mctx, char **err_message) {
905 UNUSED(name);
906 UNUSED(intoken);
907 UNUSED(outtoken);
908 UNUSED(gssctx);
909 UNUSED(mctx);
910 UNUSED(err_message);
911
912 return (ISC_R_NOTIMPLEMENTED);
913 }
914
915 isc_result_t
dst_gssapi_acceptctx(dns_gss_cred_id_t cred,const char * gssapi_keytab,isc_region_t * intoken,isc_buffer_t ** outtoken,dns_gss_ctx_id_t * ctxout,dns_name_t * principal,isc_mem_t * mctx)916 dst_gssapi_acceptctx(dns_gss_cred_id_t cred, const char *gssapi_keytab,
917 isc_region_t *intoken, isc_buffer_t **outtoken,
918 dns_gss_ctx_id_t *ctxout, dns_name_t *principal,
919 isc_mem_t *mctx) {
920 UNUSED(cred);
921 UNUSED(gssapi_keytab);
922 UNUSED(intoken);
923 UNUSED(outtoken);
924 UNUSED(ctxout);
925 UNUSED(principal);
926 UNUSED(mctx);
927
928 return (ISC_R_NOTIMPLEMENTED);
929 }
930
931 isc_result_t
dst_gssapi_deletectx(isc_mem_t * mctx,dns_gss_ctx_id_t * gssctx)932 dst_gssapi_deletectx(isc_mem_t *mctx, dns_gss_ctx_id_t *gssctx) {
933 UNUSED(mctx);
934 UNUSED(gssctx);
935 return (ISC_R_NOTIMPLEMENTED);
936 }
937
938 char *
gss_error_tostring(uint32_t major,uint32_t minor,char * buf,size_t buflen)939 gss_error_tostring(uint32_t major, uint32_t minor, char *buf, size_t buflen) {
940 snprintf(buf, buflen, "GSSAPI error: Major = %u, Minor = %u.", major,
941 minor);
942
943 return (buf);
944 }
945 #endif /* ifdef GSSAPI */
946
947 void
gss_log(int level,const char * fmt,...)948 gss_log(int level, const char *fmt, ...) {
949 va_list ap;
950
951 va_start(ap, fmt);
952 isc_log_vwrite(dns_lctx, DNS_LOGCATEGORY_GENERAL, DNS_LOGMODULE_TKEY,
953 ISC_LOG_DEBUG(level), fmt, ap);
954 va_end(ap);
955 }
956
957 /*! \file */
958