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