Coverage Report

Created: 2025-11-24 06:17

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/bind9/lib/dns/gssapictx.c
Line
Count
Source
1
/*
2
 * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
3
 *
4
 * SPDX-License-Identifier: MPL-2.0
5
 *
6
 * This Source Code Form is subject to the terms of the Mozilla Public
7
 * License, v. 2.0. If a copy of the MPL was not distributed with this
8
 * file, you can obtain one at https://mozilla.org/MPL/2.0/.
9
 *
10
 * See the COPYRIGHT file distributed with this work for additional
11
 * information regarding copyright ownership.
12
 */
13
14
#include <ctype.h>
15
#include <inttypes.h>
16
#include <stdbool.h>
17
#include <stdlib.h>
18
#include <string.h>
19
#include <time.h>
20
21
#if HAVE_GSSAPI_GSSAPI_H
22
#include <gssapi/gssapi.h>
23
#elif HAVE_GSSAPI_H
24
#include <gssapi.h>
25
#endif
26
27
#if HAVE_GSSAPI_GSSAPI_KRB5_H
28
#include <gssapi/gssapi_krb5.h>
29
#elif HAVE_GSSAPI_KRB5_H
30
#include <gssapi_krb5.h>
31
#endif
32
33
#if HAVE_KRB5_KRB5_H
34
#include <krb5/krb5.h>
35
#elif HAVE_KRB5_H
36
#include <krb5.h>
37
#endif
38
39
#include <isc/buffer.h>
40
#include <isc/dir.h>
41
#include <isc/file.h>
42
#include <isc/lex.h>
43
#include <isc/log.h>
44
#include <isc/mem.h>
45
#include <isc/once.h>
46
#include <isc/random.h>
47
#include <isc/result.h>
48
#include <isc/string.h>
49
#include <isc/time.h>
50
#include <isc/util.h>
51
52
#include <dns/fixedname.h>
53
#include <dns/keyvalues.h>
54
#include <dns/rdata.h>
55
#include <dns/rdataclass.h>
56
#include <dns/types.h>
57
58
#include <dst/gssapi.h>
59
60
#include "dst_internal.h"
61
62
#if HAVE_GSSAPI
63
64
#ifndef GSS_SPNEGO_MECHANISM
65
static unsigned char spnego_mech_oid_bytes[] = { 0x2b, 0x06, 0x01,
66
             0x05, 0x05, 0x02 };
67
static gss_OID_desc __gss_spnego_mechanism_oid_desc = {
68
  sizeof(spnego_mech_oid_bytes), spnego_mech_oid_bytes
69
};
70
#define GSS_SPNEGO_MECHANISM (&__gss_spnego_mechanism_oid_desc)
71
#endif /* ifndef GSS_SPNEGO_MECHANISM */
72
73
#define REGION_TO_GBUFFER(r, gb)          \
74
  do {                              \
75
    (gb).length = (r).length; \
76
    (gb).value = (r).base;    \
77
  } while (0)
78
79
#define GBUFFER_TO_REGION(gb, r)                        \
80
  do {                                            \
81
    (r).length = (unsigned int)(gb).length; \
82
    (r).base = (gb).value;                  \
83
  } while (0)
84
85
#define RETERR(x)                            \
86
  do {                                 \
87
    result = (x);                \
88
    if (result != ISC_R_SUCCESS) \
89
      goto out;            \
90
  } while (0)
91
92
static void
93
name_to_gbuffer(const dns_name_t *name, isc_buffer_t *buffer,
94
    gss_buffer_desc *gbuffer) {
95
  dns_name_t tname;
96
  const dns_name_t *namep;
97
  isc_region_t r;
98
  isc_result_t result;
99
100
  if (!dns_name_isabsolute(name)) {
101
    namep = name;
102
  } else {
103
    unsigned int labels;
104
    dns_name_init(&tname);
105
    labels = dns_name_countlabels(name);
106
    dns_name_getlabelsequence(name, 0, labels - 1, &tname);
107
    namep = &tname;
108
  }
109
110
  result = dns_name_totext(
111
    namep, DNS_NAME_OMITFINALDOT | DNS_NAME_PRINCIPAL, buffer);
112
  RUNTIME_CHECK(result == ISC_R_SUCCESS);
113
  isc_buffer_putuint8(buffer, 0);
114
  isc_buffer_usedregion(buffer, &r);
115
  REGION_TO_GBUFFER(r, *gbuffer);
116
}
117
118
bool
119
dst_gssapi_identitymatchesrealmkrb5(const dns_name_t *signer,
120
            const dns_name_t *name,
121
            const dns_name_t *realm, bool subdomain) {
122
  char sbuf[DNS_NAME_FORMATSIZE];
123
  char rbuf[DNS_NAME_FORMATSIZE];
124
  char *sname;
125
  char *rname;
126
  isc_buffer_t buffer;
127
  isc_result_t result;
128
129
  /*
130
   * It is far, far easier to write the names we are looking at into
131
   * a string, and do string operations on them.
132
   */
133
  isc_buffer_init(&buffer, sbuf, sizeof(sbuf));
134
  result = dns_name_totext(
135
    signer, DNS_NAME_OMITFINALDOT | DNS_NAME_PRINCIPAL, &buffer);
136
  RUNTIME_CHECK(result == ISC_R_SUCCESS);
137
  isc_buffer_putuint8(&buffer, 0);
138
  dns_name_format(realm, rbuf, sizeof(rbuf));
139
140
  /*
141
   * Find the realm portion.  This is the part after the @.  If it
142
   * does not exist, we don't have something we like, so we fail our
143
   * compare.
144
   */
145
  rname = strchr(sbuf, '@');
146
  if (rname == NULL) {
147
    return false;
148
  }
149
  *rname = '\0';
150
  rname++;
151
152
  if (strcmp(rname, rbuf) != 0) {
153
    return false;
154
  }
155
156
  /*
157
   * Find the host portion of the signer's name.  We do this by
158
   * searching for the first / character.  We then check to make
159
   * certain the instance name is "host"
160
   *
161
   * This will work for
162
   *    host/example.com@EXAMPLE.COM
163
   */
164
  sname = strchr(sbuf, '/');
165
  if (sname == NULL) {
166
    return false;
167
  }
168
  *sname = '\0';
169
  sname++;
170
  if (strcmp(sbuf, "host") != 0) {
171
    return false;
172
  }
173
174
  /*
175
   * If name is non NULL check that it matches against the
176
   * machine name as expected.
177
   */
178
  if (name != NULL) {
179
    dns_fixedname_t fixed;
180
    dns_name_t *machine;
181
182
    machine = dns_fixedname_initname(&fixed);
183
    result = dns_name_fromstring(machine, sname, dns_rootname, 0,
184
               NULL);
185
    if (result != ISC_R_SUCCESS) {
186
      return false;
187
    }
188
    if (subdomain) {
189
      return dns_name_issubdomain(name, machine);
190
    }
191
    return dns_name_equal(name, machine);
192
  }
193
194
  return true;
195
}
196
197
bool
198
dst_gssapi_identitymatchesrealmms(const dns_name_t *signer,
199
          const dns_name_t *name,
200
          const dns_name_t *realm, bool subdomain) {
201
  char sbuf[DNS_NAME_FORMATSIZE];
202
  char rbuf[DNS_NAME_FORMATSIZE];
203
  char *sname;
204
  char *rname;
205
  isc_buffer_t buffer;
206
  isc_result_t result;
207
208
  /*
209
   * It is far, far easier to write the names we are looking at into
210
   * a string, and do string operations on them.
211
   */
212
  isc_buffer_init(&buffer, sbuf, sizeof(sbuf));
213
  result = dns_name_totext(
214
    signer, DNS_NAME_OMITFINALDOT | DNS_NAME_PRINCIPAL, &buffer);
215
  RUNTIME_CHECK(result == ISC_R_SUCCESS);
216
  isc_buffer_putuint8(&buffer, 0);
217
  dns_name_format(realm, rbuf, sizeof(rbuf));
218
219
  /*
220
   * Find the realm portion.  This is the part after the @.  If it
221
   * does not exist, we don't have something we like, so we fail our
222
   * compare.
223
   */
224
  rname = strchr(sbuf, '@');
225
  if (rname == NULL) {
226
    return false;
227
  }
228
  sname = strchr(sbuf, '$');
229
  if (sname == NULL) {
230
    return false;
231
  }
232
233
  /*
234
   * Verify that the $ and @ follow one another.
235
   */
236
  if (rname - sname != 1) {
237
    return false;
238
  }
239
240
  /*
241
   * Find the host portion of the signer's name.  Zero out the $ so
242
   * it terminates the signer's name, and skip past the @ for
243
   * the realm.
244
   *
245
   * All service principals in Microsoft format seem to be in
246
   *    machinename$@EXAMPLE.COM
247
   * format.
248
   */
249
  rname++;
250
  *sname = '\0';
251
252
  if (strcmp(rname, rbuf) != 0) {
253
    return false;
254
  }
255
256
  /*
257
   * Now, we check that the realm matches (case sensitive) and that
258
   * 'name' matches against 'machinename' qualified with 'realm'.
259
   */
260
  if (name != NULL) {
261
    dns_fixedname_t fixed;
262
    dns_name_t *machine;
263
264
    machine = dns_fixedname_initname(&fixed);
265
    result = dns_name_fromstring(machine, sbuf, realm, 0, NULL);
266
    if (result != ISC_R_SUCCESS) {
267
      return false;
268
    }
269
    if (subdomain) {
270
      return dns_name_issubdomain(name, machine);
271
    }
272
    return dns_name_equal(name, machine);
273
  }
274
275
  return true;
276
}
277
278
/*
279
 * Format a gssapi error message info into a char ** on the given memory
280
 * context. This is used to return gssapi error messages back up the
281
 * call chain for reporting to the user.
282
 */
283
static void
284
gss_err_message(isc_mem_t *mctx, uint32_t major, uint32_t minor,
285
    char **err_message) {
286
  char buf[1024];
287
  char *estr;
288
289
  if (err_message == NULL || mctx == NULL) {
290
    /* the caller doesn't want any error messages */
291
    return;
292
  }
293
294
  estr = gss_error_tostring(major, minor, buf, sizeof(buf));
295
  if (estr != NULL) {
296
    (*err_message) = isc_mem_strdup(mctx, estr);
297
  }
298
}
299
300
isc_result_t
301
dst_gssapi_initctx(const dns_name_t *name, isc_buffer_t *intoken,
302
       isc_buffer_t *outtoken, dns_gss_ctx_id_t *gssctx,
303
       isc_mem_t *mctx, char **err_message) {
304
  isc_region_t r;
305
  isc_buffer_t namebuf;
306
  gss_name_t gname;
307
  OM_uint32 gret, minor, ret_flags, flags;
308
  gss_buffer_desc gintoken, *gintokenp, gouttoken = GSS_C_EMPTY_BUFFER;
309
  isc_result_t result;
310
  gss_buffer_desc gnamebuf;
311
  unsigned char array[DNS_NAME_MAXTEXT + 1];
312
313
  /* Client must pass us a valid gss_ctx_id_t here */
314
  REQUIRE(gssctx != NULL);
315
  REQUIRE(mctx != NULL);
316
317
  isc_buffer_init(&namebuf, array, sizeof(array));
318
  name_to_gbuffer(name, &namebuf, &gnamebuf);
319
320
  /* Get the name as a GSS name */
321
  gret = gss_import_name(&minor, &gnamebuf, GSS_C_NO_OID, &gname);
322
  if (gret != GSS_S_COMPLETE) {
323
    gss_err_message(mctx, gret, minor, err_message);
324
    result = ISC_R_FAILURE;
325
    goto out;
326
  }
327
328
  if (intoken != NULL) {
329
    /* Don't call gss_release_buffer for gintoken! */
330
    REGION_TO_GBUFFER(*intoken, gintoken);
331
    gintokenp = &gintoken;
332
  } else {
333
    gintokenp = NULL;
334
  }
335
336
  /*
337
   * Note that we don't set GSS_C_SEQUENCE_FLAG as Windows DNS
338
   * servers don't like it.
339
   */
340
  flags = GSS_C_REPLAY_FLAG | GSS_C_MUTUAL_FLAG | GSS_C_INTEG_FLAG;
341
342
  gret = gss_init_sec_context(
343
    &minor, GSS_C_NO_CREDENTIAL, (gss_ctx_id_t *)gssctx, gname,
344
    GSS_SPNEGO_MECHANISM, flags, 0, NULL, gintokenp, NULL,
345
    &gouttoken, &ret_flags, NULL);
346
347
  if (gret != GSS_S_COMPLETE && gret != GSS_S_CONTINUE_NEEDED) {
348
    gss_err_message(mctx, gret, minor, err_message);
349
    if (err_message != NULL && *err_message != NULL) {
350
      gss_log(3, "Failure initiating security context: %s",
351
        *err_message);
352
    } else {
353
      gss_log(3, "Failure initiating security context");
354
    }
355
356
    result = ISC_R_FAILURE;
357
    goto out;
358
  }
359
360
  /*
361
   * XXXSRA Not handled yet: RFC 3645 3.1.1: check ret_flags
362
   * MUTUAL and INTEG flags, fail if either not set.
363
   */
364
365
  /*
366
   * RFC 2744 states the a valid output token has a non-zero length.
367
   */
368
  if (gouttoken.length != 0U) {
369
    GBUFFER_TO_REGION(gouttoken, r);
370
    RETERR(isc_buffer_copyregion(outtoken, &r));
371
  }
372
373
  if (gret == GSS_S_COMPLETE) {
374
    result = ISC_R_SUCCESS;
375
  } else {
376
    result = DNS_R_CONTINUE;
377
  }
378
379
out:
380
  if (gouttoken.length != 0U) {
381
    (void)gss_release_buffer(&minor, &gouttoken);
382
  }
383
  (void)gss_release_name(&minor, &gname);
384
  return result;
385
}
386
387
isc_result_t
388
dst_gssapi_acceptctx(const char *gssapi_keytab, isc_region_t *intoken,
389
         isc_buffer_t **outtoken, dns_gss_ctx_id_t *ctxout,
390
         dns_name_t *principal, isc_mem_t *mctx) {
391
  isc_region_t r;
392
  isc_buffer_t namebuf;
393
  gss_buffer_desc gnamebuf = GSS_C_EMPTY_BUFFER, gintoken,
394
      gouttoken = GSS_C_EMPTY_BUFFER;
395
  OM_uint32 gret, minor;
396
  gss_ctx_id_t context = GSS_C_NO_CONTEXT;
397
  gss_name_t gname = NULL;
398
  isc_result_t result;
399
  char buf[1024];
400
401
  REQUIRE(outtoken != NULL && *outtoken == NULL);
402
403
  REGION_TO_GBUFFER(*intoken, gintoken);
404
405
  if (*ctxout == NULL) {
406
    context = GSS_C_NO_CONTEXT;
407
  } else {
408
    context = *ctxout;
409
  }
410
411
  if (gssapi_keytab != NULL) {
412
#if HAVE_GSSAPI_GSSAPI_KRB5_H || HAVE_GSSAPI_KRB5_H
413
    gret = gsskrb5_register_acceptor_identity(gssapi_keytab);
414
    if (gret != GSS_S_COMPLETE) {
415
      gss_log(3,
416
        "failed "
417
        "gsskrb5_register_acceptor_identity(%s): %s",
418
        gssapi_keytab,
419
        gss_error_tostring(gret, 0, buf, sizeof(buf)));
420
      return DNS_R_INVALIDTKEY;
421
    }
422
#else
423
    /*
424
     * Minimize memory leakage by only setting KRB5_KTNAME
425
     * if it needs to change.
426
     */
427
    const char *old = getenv("KRB5_KTNAME");
428
    if (old == NULL || strcmp(old, gssapi_keytab) != 0) {
429
      size_t size;
430
      char *kt;
431
432
      size = strlen(gssapi_keytab) + 13;
433
      kt = malloc(size);
434
      if (kt == NULL) {
435
        return ISC_R_NOMEMORY;
436
      }
437
      snprintf(kt, size, "KRB5_KTNAME=%s", gssapi_keytab);
438
      if (putenv(kt) != 0) {
439
        return ISC_R_NOMEMORY;
440
      }
441
    }
442
#endif
443
  }
444
445
  gret = gss_accept_sec_context(&minor, &context, GSS_C_NO_CREDENTIAL,
446
              &gintoken, GSS_C_NO_CHANNEL_BINDINGS,
447
              &gname, NULL, &gouttoken, NULL, NULL,
448
              NULL);
449
450
  result = ISC_R_FAILURE;
451
452
  switch (gret) {
453
  case GSS_S_COMPLETE:
454
  case GSS_S_CONTINUE_NEEDED:
455
    break;
456
  case GSS_S_DEFECTIVE_TOKEN:
457
  case GSS_S_DEFECTIVE_CREDENTIAL:
458
  case GSS_S_BAD_SIG:
459
  case GSS_S_DUPLICATE_TOKEN:
460
  case GSS_S_OLD_TOKEN:
461
  case GSS_S_NO_CRED:
462
  case GSS_S_CREDENTIALS_EXPIRED:
463
  case GSS_S_BAD_BINDINGS:
464
  case GSS_S_NO_CONTEXT:
465
  case GSS_S_BAD_MECH:
466
  case GSS_S_FAILURE:
467
    result = DNS_R_INVALIDTKEY;
468
  /* fall through */
469
  default:
470
    gss_log(3, "failed gss_accept_sec_context: %s",
471
      gss_error_tostring(gret, minor, buf, sizeof(buf)));
472
    if (gouttoken.length > 0U) {
473
      (void)gss_release_buffer(&minor, &gouttoken);
474
    }
475
    return result;
476
  }
477
478
  if (gouttoken.length > 0U) {
479
    isc_buffer_allocate(mctx, outtoken,
480
            (unsigned int)gouttoken.length);
481
    GBUFFER_TO_REGION(gouttoken, r);
482
    RETERR(isc_buffer_copyregion(*outtoken, &r));
483
    (void)gss_release_buffer(&minor, &gouttoken);
484
  }
485
486
  if (gret == GSS_S_COMPLETE) {
487
    gret = gss_display_name(&minor, gname, &gnamebuf, NULL);
488
    if (gret != GSS_S_COMPLETE) {
489
      gss_log(3, "failed gss_display_name: %s",
490
        gss_error_tostring(gret, minor, buf,
491
               sizeof(buf)));
492
      RETERR(ISC_R_FAILURE);
493
    }
494
495
    /*
496
     * Compensate for a bug in Solaris8's implementation
497
     * of gss_display_name().  Should be harmless in any
498
     * case, since principal names really should not
499
     * contain null characters.
500
     */
501
    if (gnamebuf.length > 0U &&
502
        ((char *)gnamebuf.value)[gnamebuf.length - 1] == '\0')
503
    {
504
      gnamebuf.length--;
505
    }
506
507
    gss_log(3, "gss-api source name (accept) is %.*s",
508
      (int)gnamebuf.length, (char *)gnamebuf.value);
509
510
    GBUFFER_TO_REGION(gnamebuf, r);
511
    isc_buffer_init(&namebuf, r.base, r.length);
512
    isc_buffer_add(&namebuf, r.length);
513
514
    RETERR(dns_name_fromtext(principal, &namebuf, dns_rootname, 0));
515
516
    if (gnamebuf.length != 0U) {
517
      gret = gss_release_buffer(&minor, &gnamebuf);
518
      if (gret != GSS_S_COMPLETE) {
519
        gss_log(3, "failed gss_release_buffer: %s",
520
          gss_error_tostring(gret, minor, buf,
521
                 sizeof(buf)));
522
      }
523
    }
524
  } else {
525
    result = DNS_R_CONTINUE;
526
  }
527
528
  *ctxout = context;
529
530
out:
531
  if (gname != NULL) {
532
    gret = gss_release_name(&minor, &gname);
533
    if (gret != GSS_S_COMPLETE) {
534
      gss_log(3, "failed gss_release_name: %s",
535
        gss_error_tostring(gret, minor, buf,
536
               sizeof(buf)));
537
    }
538
  }
539
540
  return result;
541
}
542
543
isc_result_t
544
dst_gssapi_deletectx(isc_mem_t *mctx, dns_gss_ctx_id_t *gssctx) {
545
  OM_uint32 gret, minor;
546
  char buf[1024];
547
548
  UNUSED(mctx);
549
550
  REQUIRE(gssctx != NULL && *gssctx != NULL);
551
552
  /* Delete the context from the GSS provider */
553
  gret = gss_delete_sec_context(&minor, (gss_ctx_id_t *)gssctx,
554
              GSS_C_NO_BUFFER);
555
  if (gret != GSS_S_COMPLETE) {
556
    /* Log the error, but still free the context's memory */
557
    gss_log(3, "Failure deleting security context %s",
558
      gss_error_tostring(gret, minor, buf, sizeof(buf)));
559
  }
560
  return ISC_R_SUCCESS;
561
}
562
563
char *
564
gss_error_tostring(uint32_t major, uint32_t minor, char *buf, size_t buflen) {
565
  gss_buffer_desc msg_minor = GSS_C_EMPTY_BUFFER,
566
      msg_major = GSS_C_EMPTY_BUFFER;
567
  OM_uint32 msg_ctx, minor_stat;
568
569
  /* Handle major status */
570
  msg_ctx = 0;
571
  (void)gss_display_status(&minor_stat, major, GSS_C_GSS_CODE,
572
         GSS_C_NULL_OID, &msg_ctx, &msg_major);
573
574
  /* Handle minor status */
575
  msg_ctx = 0;
576
  (void)gss_display_status(&minor_stat, minor, GSS_C_MECH_CODE,
577
         GSS_C_NULL_OID, &msg_ctx, &msg_minor);
578
579
  snprintf(buf, buflen, "GSSAPI error: Major = %s, Minor = %s.",
580
     (char *)msg_major.value, (char *)msg_minor.value);
581
582
  if (msg_major.length != 0U) {
583
    (void)gss_release_buffer(&minor_stat, &msg_major);
584
  }
585
  if (msg_minor.length != 0U) {
586
    (void)gss_release_buffer(&minor_stat, &msg_minor);
587
  }
588
  return buf;
589
}
590
591
#else
592
593
bool
594
dst_gssapi_identitymatchesrealmkrb5(const dns_name_t *signer,
595
            const dns_name_t *name,
596
0
            const dns_name_t *realm, bool subdomain) {
597
0
  UNUSED(signer);
598
0
  UNUSED(name);
599
0
  UNUSED(realm);
600
0
  UNUSED(subdomain);
601
602
0
  return false;
603
0
}
604
605
bool
606
dst_gssapi_identitymatchesrealmms(const dns_name_t *signer,
607
          const dns_name_t *name,
608
0
          const dns_name_t *realm, bool subdomain) {
609
0
  UNUSED(signer);
610
0
  UNUSED(name);
611
0
  UNUSED(realm);
612
0
  UNUSED(subdomain);
613
614
0
  return false;
615
0
}
616
617
isc_result_t
618
dst_gssapi_initctx(const dns_name_t *name, isc_buffer_t *intoken,
619
       isc_buffer_t *outtoken, dns_gss_ctx_id_t *gssctx,
620
0
       isc_mem_t *mctx, char **err_message) {
621
0
  UNUSED(name);
622
0
  UNUSED(intoken);
623
0
  UNUSED(outtoken);
624
0
  UNUSED(gssctx);
625
0
  UNUSED(mctx);
626
0
  UNUSED(err_message);
627
628
0
  return ISC_R_NOTIMPLEMENTED;
629
0
}
630
631
isc_result_t
632
dst_gssapi_acceptctx(const char *gssapi_keytab, isc_region_t *intoken,
633
         isc_buffer_t **outtoken, dns_gss_ctx_id_t *ctxout,
634
0
         dns_name_t *principal, isc_mem_t *mctx) {
635
0
  UNUSED(gssapi_keytab);
636
0
  UNUSED(intoken);
637
0
  UNUSED(outtoken);
638
0
  UNUSED(ctxout);
639
0
  UNUSED(principal);
640
0
  UNUSED(mctx);
641
642
0
  return ISC_R_NOTIMPLEMENTED;
643
0
}
644
645
isc_result_t
646
0
dst_gssapi_deletectx(isc_mem_t *mctx, dns_gss_ctx_id_t *gssctx) {
647
0
  UNUSED(mctx);
648
0
  UNUSED(gssctx);
649
0
  return ISC_R_NOTIMPLEMENTED;
650
0
}
651
652
char *
653
0
gss_error_tostring(uint32_t major, uint32_t minor, char *buf, size_t buflen) {
654
0
  snprintf(buf, buflen, "GSSAPI error: Major = %u, Minor = %u.", major,
655
0
     minor);
656
657
0
  return buf;
658
0
}
659
660
#endif
661
662
void
663
0
gss_log(int level, const char *fmt, ...) {
664
0
  va_list ap;
665
666
0
  va_start(ap, fmt);
667
0
  isc_log_vwrite(DNS_LOGCATEGORY_GENERAL, DNS_LOGMODULE_TKEY,
668
0
           ISC_LOG_DEBUG(level), fmt, ap);
669
  va_end(ap);
670
0
}