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