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