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