]> CyberLeo.Net >> Repos - FreeBSD/releng/9.3.git/blob - contrib/bind9/lib/dns/gssapictx.c
Fix BIND remote Denial of Service vulnerability. [SA-16:34]
[FreeBSD/releng/9.3.git] / contrib / bind9 / lib / dns / gssapictx.c
1 /*
2  * Copyright (C) 2004-2013  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 = 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) != 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) != 0) {
244                 gss_log(ISC_LOG_ERROR, "default realm from krb5.conf (%s) "
245                         "does not match tkey-gssapi-credential (%s)",
246                         krb5_realm, 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                 (void)gss_release_buffer(&minor, &gouttoken);
637         }
638
639         if (gret == GSS_S_COMPLETE)
640                 result = ISC_R_SUCCESS;
641         else
642                 result = DNS_R_CONTINUE;
643
644  out:
645         (void)gss_release_name(&minor, &gname);
646         return (result);
647 #else
648         UNUSED(name);
649         UNUSED(intoken);
650         UNUSED(outtoken);
651         UNUSED(gssctx);
652         UNUSED(mctx);
653         UNUSED(err_message);
654
655         return (ISC_R_NOTIMPLEMENTED);
656 #endif
657 }
658
659 isc_result_t
660 dst_gssapi_acceptctx(gss_cred_id_t cred,
661                      const char *gssapi_keytab,
662                      isc_region_t *intoken, isc_buffer_t **outtoken,
663                      gss_ctx_id_t *ctxout, dns_name_t *principal,
664                      isc_mem_t *mctx)
665 {
666 #ifdef GSSAPI
667         isc_region_t r;
668         isc_buffer_t namebuf;
669         gss_buffer_desc gnamebuf = GSS_C_EMPTY_BUFFER, gintoken,
670                         gouttoken = GSS_C_EMPTY_BUFFER;
671         OM_uint32 gret, minor;
672         gss_ctx_id_t context = GSS_C_NO_CONTEXT;
673         gss_name_t gname = NULL;
674         isc_result_t result;
675         char buf[1024];
676
677         REQUIRE(outtoken != NULL && *outtoken == NULL);
678
679         REGION_TO_GBUFFER(*intoken, gintoken);
680
681         if (*ctxout == NULL)
682                 context = GSS_C_NO_CONTEXT;
683         else
684                 context = *ctxout;
685
686         if (gssapi_keytab != NULL) {
687 #if defined(ISC_PLATFORM_GSSAPI_KRB5_HEADER) || defined(WIN32)
688                 gret = gsskrb5_register_acceptor_identity(gssapi_keytab);
689                 if (gret != GSS_S_COMPLETE) {
690                         gss_log(3, "failed "
691                                 "gsskrb5_register_acceptor_identity(%s): %s",
692                                 gssapi_keytab,
693                                 gss_error_tostring(gret, 0, buf, sizeof(buf)));
694                         return (DNS_R_INVALIDTKEY);
695                 }
696 #else
697                 /*
698                  * Minimize memory leakage by only setting KRB5_KTNAME
699                  * if it needs to change.
700                  */
701                 const char *old = getenv("KRB5_KTNAME");
702                 if (old == NULL || strcmp(old, gssapi_keytab) != 0) {
703                         char *kt = malloc(strlen(gssapi_keytab) + 13);
704                         if (kt == NULL)
705                                 return (ISC_R_NOMEMORY);
706                         sprintf(kt, "KRB5_KTNAME=%s", gssapi_keytab);
707                         if (putenv(kt) != 0)
708                                 return (ISC_R_NOMEMORY);
709                 }
710 #endif
711         }
712
713         log_cred(cred);
714
715         gret = gss_accept_sec_context(&minor, &context, cred, &gintoken,
716                                       GSS_C_NO_CHANNEL_BINDINGS, &gname,
717                                       NULL, &gouttoken, NULL, NULL, NULL);
718
719         result = ISC_R_FAILURE;
720
721         switch (gret) {
722         case GSS_S_COMPLETE:
723                 result = ISC_R_SUCCESS;
724                 break;
725         case GSS_S_CONTINUE_NEEDED:
726                 result = DNS_R_CONTINUE;
727                 break;
728         case GSS_S_DEFECTIVE_TOKEN:
729         case GSS_S_DEFECTIVE_CREDENTIAL:
730         case GSS_S_BAD_SIG:
731         case GSS_S_DUPLICATE_TOKEN:
732         case GSS_S_OLD_TOKEN:
733         case GSS_S_NO_CRED:
734         case GSS_S_CREDENTIALS_EXPIRED:
735         case GSS_S_BAD_BINDINGS:
736         case GSS_S_NO_CONTEXT:
737         case GSS_S_BAD_MECH:
738         case GSS_S_FAILURE:
739                 result = DNS_R_INVALIDTKEY;
740                 /* fall through */
741         default:
742                 gss_log(3, "failed gss_accept_sec_context: %s",
743                         gss_error_tostring(gret, minor, buf, sizeof(buf)));
744                 return (result);
745         }
746
747         if (gouttoken.length > 0U) {
748                 RETERR(isc_buffer_allocate(mctx, outtoken,
749                                            (unsigned int)gouttoken.length));
750                 GBUFFER_TO_REGION(gouttoken, r);
751                 RETERR(isc_buffer_copyregion(*outtoken, &r));
752                 (void)gss_release_buffer(&minor, &gouttoken);
753         }
754
755         if (gret == GSS_S_COMPLETE) {
756                 gret = gss_display_name(&minor, gname, &gnamebuf, NULL);
757                 if (gret != GSS_S_COMPLETE) {
758                         gss_log(3, "failed gss_display_name: %s",
759                                 gss_error_tostring(gret, minor,
760                                                    buf, sizeof(buf)));
761                         RETERR(ISC_R_FAILURE);
762                 }
763
764                 /*
765                  * Compensate for a bug in Solaris8's implementation
766                  * of gss_display_name().  Should be harmless in any
767                  * case, since principal names really should not
768                  * contain null characters.
769                  */
770                 if (gnamebuf.length > 0U &&
771                     ((char *)gnamebuf.value)[gnamebuf.length - 1] == '\0')
772                         gnamebuf.length--;
773
774                 gss_log(3, "gss-api source name (accept) is %.*s",
775                         (int)gnamebuf.length, (char *)gnamebuf.value);
776
777                 GBUFFER_TO_REGION(gnamebuf, r);
778                 isc_buffer_init(&namebuf, r.base, r.length);
779                 isc_buffer_add(&namebuf, r.length);
780
781                 RETERR(dns_name_fromtext(principal, &namebuf, dns_rootname,
782                                          0, NULL));
783
784                 if (gnamebuf.length != 0U) {
785                         gret = gss_release_buffer(&minor, &gnamebuf);
786                         if (gret != GSS_S_COMPLETE)
787                                 gss_log(3, "failed gss_release_buffer: %s",
788                                         gss_error_tostring(gret, minor, buf,
789                                                            sizeof(buf)));
790                 }
791         }
792
793         *ctxout = context;
794
795  out:
796         if (gname != NULL) {
797                 gret = gss_release_name(&minor, &gname);
798                 if (gret != GSS_S_COMPLETE)
799                         gss_log(3, "failed gss_release_name: %s",
800                                 gss_error_tostring(gret, minor, buf,
801                                                    sizeof(buf)));
802         }
803
804         return (result);
805 #else
806         UNUSED(cred);
807         UNUSED(gssapi_keytab);
808         UNUSED(intoken);
809         UNUSED(outtoken);
810         UNUSED(ctxout);
811         UNUSED(principal);
812         UNUSED(mctx);
813
814         return (ISC_R_NOTIMPLEMENTED);
815 #endif
816 }
817
818 isc_result_t
819 dst_gssapi_deletectx(isc_mem_t *mctx, gss_ctx_id_t *gssctx)
820 {
821 #ifdef GSSAPI
822         OM_uint32 gret, minor;
823         char buf[1024];
824
825         UNUSED(mctx);
826
827         REQUIRE(gssctx != NULL && *gssctx != NULL);
828
829         /* Delete the context from the GSS provider */
830         gret = gss_delete_sec_context(&minor, gssctx, GSS_C_NO_BUFFER);
831         if (gret != GSS_S_COMPLETE) {
832                 /* Log the error, but still free the context's memory */
833                 gss_log(3, "Failure deleting security context %s",
834                         gss_error_tostring(gret, minor, buf, sizeof(buf)));
835         }
836         return(ISC_R_SUCCESS);
837 #else
838         UNUSED(mctx);
839         UNUSED(gssctx);
840         return (ISC_R_NOTIMPLEMENTED);
841 #endif
842 }
843
844 char *
845 gss_error_tostring(isc_uint32_t major, isc_uint32_t minor,
846                    char *buf, size_t buflen) {
847 #ifdef GSSAPI
848         gss_buffer_desc msg_minor = GSS_C_EMPTY_BUFFER,
849                         msg_major = GSS_C_EMPTY_BUFFER;
850         OM_uint32 msg_ctx, minor_stat;
851
852         /* Handle major status */
853         msg_ctx = 0;
854         (void)gss_display_status(&minor_stat, major, GSS_C_GSS_CODE,
855                                  GSS_C_NULL_OID, &msg_ctx, &msg_major);
856
857         /* Handle minor status */
858         msg_ctx = 0;
859         (void)gss_display_status(&minor_stat, minor, GSS_C_MECH_CODE,
860                                  GSS_C_NULL_OID, &msg_ctx, &msg_minor);
861
862         snprintf(buf, buflen, "GSSAPI error: Major = %s, Minor = %s.",
863                 (char *)msg_major.value, (char *)msg_minor.value);
864
865         if (msg_major.length != 0U)
866                 (void)gss_release_buffer(&minor_stat, &msg_major);
867         if (msg_minor.length != 0U)
868                 (void)gss_release_buffer(&minor_stat, &msg_minor);
869         return(buf);
870 #else
871         snprintf(buf, buflen, "GSSAPI error: Major = %u, Minor = %u.",
872                  major, minor);
873
874         return (buf);
875 #endif
876 }
877
878 void
879 gss_log(int level, const char *fmt, ...) {
880         va_list ap;
881
882         va_start(ap, fmt);
883         isc_log_vwrite(dns_lctx, DNS_LOGCATEGORY_GENERAL,
884                        DNS_LOGMODULE_TKEY, ISC_LOG_DEBUG(level), fmt, ap);
885         va_end(ap);
886 }
887
888 /*! \file */