1 /* $NetBSD: dst_parse.c,v 1.8 2014/12/10 04:37:58 christos Exp $ */
4 * Portions Copyright (C) 2004-2014 Internet Systems Consortium, Inc. ("ISC")
5 * Portions Copyright (C) 1999-2002 Internet Software Consortium.
7 * Permission to use, copy, modify, and/or distribute this software for any
8 * purpose with or without fee is hereby granted, provided that the above
9 * copyright notice and this permission notice appear in all copies.
11 * THE SOFTWARE IS PROVIDED "AS IS" AND ISC AND NETWORK ASSOCIATES DISCLAIMS
12 * ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED
13 * WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE
14 * FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
15 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
16 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR
17 * IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
19 * Portions Copyright (C) 1995-2000 by Network Associates, Inc.
21 * Permission to use, copy, modify, and/or distribute this software for any
22 * purpose with or without fee is hereby granted, provided that the above
23 * copyright notice and this permission notice appear in all copies.
25 * THE SOFTWARE IS PROVIDED "AS IS" AND ISC AND NETWORK ASSOCIATES DISCLAIMS
26 * ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED
27 * WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE
28 * FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
29 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
30 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR
31 * IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
35 * Principal Author: Brian Wellington
36 * Id: dst_parse.c,v 1.29 2011/08/18 23:46:35 tbox Exp
41 #include <isc/base64.h>
43 #include <isc/fsaccess.h>
46 #include <isc/stdtime.h>
47 #include <isc/string.h>
54 #include "dst_internal.h"
55 #include "dst_parse.h"
56 #include "dst/result.h"
58 #define DST_AS_STR(t) ((t).value.as_textregion.base)
60 #define PRIVATE_KEY_STR "Private-key-format:"
61 #define ALGORITHM_STR "Algorithm:"
63 #define TIMING_NTAGS (DST_MAX_TIMES + 1)
64 static const char *timetags
[TIMING_NTAGS
] = {
74 #define NUMERIC_NTAGS (DST_MAX_NUMERIC + 1)
75 static const char *numerictags
[NUMERIC_NTAGS
] = {
87 static struct parse_map map
[] = {
88 {TAG_RSA_MODULUS
, "Modulus:"},
89 {TAG_RSA_PUBLICEXPONENT
, "PublicExponent:"},
90 {TAG_RSA_PRIVATEEXPONENT
, "PrivateExponent:"},
91 {TAG_RSA_PRIME1
, "Prime1:"},
92 {TAG_RSA_PRIME2
, "Prime2:"},
93 {TAG_RSA_EXPONENT1
, "Exponent1:"},
94 {TAG_RSA_EXPONENT2
, "Exponent2:"},
95 {TAG_RSA_COEFFICIENT
, "Coefficient:"},
96 {TAG_RSA_ENGINE
, "Engine:" },
97 {TAG_RSA_LABEL
, "Label:" },
99 {TAG_DH_PRIME
, "Prime(p):"},
100 {TAG_DH_GENERATOR
, "Generator(g):"},
101 {TAG_DH_PRIVATE
, "Private_value(x):"},
102 {TAG_DH_PUBLIC
, "Public_value(y):"},
104 {TAG_DSA_PRIME
, "Prime(p):"},
105 {TAG_DSA_SUBPRIME
, "Subprime(q):"},
106 {TAG_DSA_BASE
, "Base(g):"},
107 {TAG_DSA_PRIVATE
, "Private_value(x):"},
108 {TAG_DSA_PUBLIC
, "Public_value(y):"},
110 {TAG_GOST_PRIVASN1
, "GostAsn1:"},
111 {TAG_GOST_PRIVRAW
, "PrivateKey:"},
113 {TAG_ECDSA_PRIVATEKEY
, "PrivateKey:"},
114 {TAG_ECDSA_ENGINE
, "Engine:" },
115 {TAG_ECDSA_LABEL
, "Label:" },
117 {TAG_HMACMD5_KEY
, "Key:"},
118 {TAG_HMACMD5_BITS
, "Bits:"},
120 {TAG_HMACSHA1_KEY
, "Key:"},
121 {TAG_HMACSHA1_BITS
, "Bits:"},
123 {TAG_HMACSHA224_KEY
, "Key:"},
124 {TAG_HMACSHA224_BITS
, "Bits:"},
126 {TAG_HMACSHA256_KEY
, "Key:"},
127 {TAG_HMACSHA256_BITS
, "Bits:"},
129 {TAG_HMACSHA384_KEY
, "Key:"},
130 {TAG_HMACSHA384_BITS
, "Bits:"},
132 {TAG_HMACSHA512_KEY
, "Key:"},
133 {TAG_HMACSHA512_BITS
, "Bits:"},
139 find_value(const char *s
, const unsigned int alg
) {
142 for (i
= 0; map
[i
].tag
!= NULL
; i
++) {
143 if (strcasecmp(s
, map
[i
].tag
) == 0 &&
144 (TAG_ALG(map
[i
].value
) == alg
))
145 return (map
[i
].value
);
151 find_tag(const int value
) {
155 if (map
[i
].tag
== NULL
)
157 else if (value
== map
[i
].value
)
163 find_metadata(const char *s
, const char *tags
[], int ntags
) {
166 for (i
= 0; i
< ntags
; i
++) {
167 if (strcasecmp(s
, tags
[i
]) == 0)
175 find_timedata(const char *s
) {
176 return (find_metadata(s
, timetags
, TIMING_NTAGS
));
180 find_numericdata(const char *s
) {
181 return (find_metadata(s
, numerictags
, NUMERIC_NTAGS
));
185 check_rsa(const dst_private_t
*priv
, isc_boolean_t external
) {
187 isc_boolean_t have
[RSA_NTAGS
];
192 return ((priv
->nelements
== 0) ? 0 : -1);
194 for (i
= 0; i
< RSA_NTAGS
; i
++)
197 for (j
= 0; j
< priv
->nelements
; j
++) {
198 for (i
= 0; i
< RSA_NTAGS
; i
++)
199 if (priv
->elements
[j
].tag
== TAG(DST_ALG_RSAMD5
, i
))
207 mask
<<= sizeof(mask
) * 8 - TAG_SHIFT
;
208 mask
>>= sizeof(mask
) * 8 - TAG_SHIFT
;
210 if (have
[TAG_RSA_ENGINE
& mask
])
211 ok
= have
[TAG_RSA_MODULUS
& mask
] &&
212 have
[TAG_RSA_PUBLICEXPONENT
& mask
] &&
213 have
[TAG_RSA_LABEL
& mask
];
215 ok
= have
[TAG_RSA_MODULUS
& mask
] &&
216 have
[TAG_RSA_PUBLICEXPONENT
& mask
] &&
217 have
[TAG_RSA_PRIVATEEXPONENT
& mask
] &&
218 have
[TAG_RSA_PRIME1
& mask
] &&
219 have
[TAG_RSA_PRIME2
& mask
] &&
220 have
[TAG_RSA_EXPONENT1
& mask
] &&
221 have
[TAG_RSA_EXPONENT2
& mask
] &&
222 have
[TAG_RSA_COEFFICIENT
& mask
];
223 return (ok
? 0 : -1 );
227 check_dh(const dst_private_t
*priv
) {
229 if (priv
->nelements
!= DH_NTAGS
)
231 for (i
= 0; i
< DH_NTAGS
; i
++) {
232 for (j
= 0; j
< priv
->nelements
; j
++)
233 if (priv
->elements
[j
].tag
== TAG(DST_ALG_DH
, i
))
235 if (j
== priv
->nelements
)
242 check_dsa(const dst_private_t
*priv
, isc_boolean_t external
) {
246 return ((priv
->nelements
== 0)? 0 : -1);
248 if (priv
->nelements
!= DSA_NTAGS
)
251 for (i
= 0; i
< DSA_NTAGS
; i
++) {
252 for (j
= 0; j
< priv
->nelements
; j
++)
253 if (priv
->elements
[j
].tag
== TAG(DST_ALG_DSA
, i
))
255 if (j
== priv
->nelements
)
262 check_gost(const dst_private_t
*priv
, isc_boolean_t external
) {
265 return ((priv
->nelements
== 0)? 0 : -1);
267 if (priv
->nelements
!= GOST_NTAGS
)
269 if ((priv
->elements
[0].tag
!= TAG(DST_ALG_ECCGOST
, 0)) &&
270 (priv
->elements
[0].tag
!= TAG(DST_ALG_ECCGOST
, 1)))
276 check_ecdsa(const dst_private_t
*priv
, isc_boolean_t external
) {
278 isc_boolean_t have
[ECDSA_NTAGS
];
283 return ((priv
->nelements
== 0) ? 0 : -1);
285 for (i
= 0; i
< ECDSA_NTAGS
; i
++)
287 for (j
= 0; j
< priv
->nelements
; j
++) {
288 for (i
= 0; i
< ECDSA_NTAGS
; i
++)
289 if (priv
->elements
[j
].tag
== TAG(DST_ALG_ECDSA256
, i
))
291 if (i
== ECDSA_NTAGS
)
297 mask
<<= sizeof(mask
) * 8 - TAG_SHIFT
;
298 mask
>>= sizeof(mask
) * 8 - TAG_SHIFT
;
300 if (have
[TAG_ECDSA_ENGINE
& mask
])
301 ok
= have
[TAG_ECDSA_LABEL
& mask
];
303 ok
= have
[TAG_ECDSA_PRIVATEKEY
& mask
];
304 return (ok
? 0 : -1 );
308 check_hmac_md5(const dst_private_t
*priv
, isc_boolean_t old
) {
311 if (priv
->nelements
!= HMACMD5_NTAGS
) {
313 * If this is a good old format and we are accepting
314 * the old format return success.
316 if (old
&& priv
->nelements
== OLD_HMACMD5_NTAGS
&&
317 priv
->elements
[0].tag
== TAG_HMACMD5_KEY
)
322 * We must be new format at this point.
324 for (i
= 0; i
< HMACMD5_NTAGS
; i
++) {
325 for (j
= 0; j
< priv
->nelements
; j
++)
326 if (priv
->elements
[j
].tag
== TAG(DST_ALG_HMACMD5
, i
))
328 if (j
== priv
->nelements
)
335 check_hmac_sha(const dst_private_t
*priv
, unsigned int ntags
,
339 if (priv
->nelements
!= ntags
)
341 for (i
= 0; i
< ntags
; i
++) {
342 for (j
= 0; j
< priv
->nelements
; j
++)
343 if (priv
->elements
[j
].tag
== TAG(alg
, i
))
345 if (j
== priv
->nelements
)
352 check_data(const dst_private_t
*priv
, const unsigned int alg
,
353 isc_boolean_t old
, isc_boolean_t external
)
355 /* XXXVIX this switch statement is too sparse to gen a jump table. */
358 case DST_ALG_RSASHA1
:
359 case DST_ALG_NSEC3RSASHA1
:
360 case DST_ALG_RSASHA256
:
361 case DST_ALG_RSASHA512
:
362 return (check_rsa(priv
, external
));
364 return (check_dh(priv
));
366 case DST_ALG_NSEC3DSA
:
367 return (check_dsa(priv
, external
));
368 case DST_ALG_ECCGOST
:
369 return (check_gost(priv
, external
));
370 case DST_ALG_ECDSA256
:
371 case DST_ALG_ECDSA384
:
372 return (check_ecdsa(priv
, external
));
373 case DST_ALG_HMACMD5
:
374 return (check_hmac_md5(priv
, old
));
375 case DST_ALG_HMACSHA1
:
376 return (check_hmac_sha(priv
, HMACSHA1_NTAGS
, alg
));
377 case DST_ALG_HMACSHA224
:
378 return (check_hmac_sha(priv
, HMACSHA224_NTAGS
, alg
));
379 case DST_ALG_HMACSHA256
:
380 return (check_hmac_sha(priv
, HMACSHA256_NTAGS
, alg
));
381 case DST_ALG_HMACSHA384
:
382 return (check_hmac_sha(priv
, HMACSHA384_NTAGS
, alg
));
383 case DST_ALG_HMACSHA512
:
384 return (check_hmac_sha(priv
, HMACSHA512_NTAGS
, alg
));
386 return (DST_R_UNSUPPORTEDALG
);
391 dst__privstruct_free(dst_private_t
*priv
, isc_mem_t
*mctx
) {
396 for (i
= 0; i
< priv
->nelements
; i
++) {
397 if (priv
->elements
[i
].data
== NULL
)
399 memset(priv
->elements
[i
].data
, 0, MAXFIELDSIZE
);
400 isc_mem_put(mctx
, priv
->elements
[i
].data
, MAXFIELDSIZE
);
406 dst__privstruct_parse(dst_key_t
*key
, unsigned int alg
, isc_lex_t
*lex
,
407 isc_mem_t
*mctx
, dst_private_t
*priv
)
409 int n
= 0, major
, minor
, check
;
412 unsigned char *data
= NULL
;
413 unsigned int opt
= ISC_LEXOPT_EOL
;
416 isc_boolean_t external
= ISC_FALSE
;
418 REQUIRE(priv
!= NULL
);
421 memset(priv
->elements
, 0, sizeof(priv
->elements
));
423 #define NEXTTOKEN(lex, opt, token) \
425 ret = isc_lex_gettoken(lex, opt, token); \
426 if (ret != ISC_R_SUCCESS) \
428 } while (/*CONSTCOND*/0)
430 #define READLINE(lex, opt, token) \
432 ret = isc_lex_gettoken(lex, opt, token); \
433 if (ret == ISC_R_EOF) \
435 else if (ret != ISC_R_SUCCESS) \
437 } while ((*token).type != isc_tokentype_eol)
440 * Read the description line.
442 NEXTTOKEN(lex
, opt
, &token
);
443 if (token
.type
!= isc_tokentype_string
||
444 strcmp(DST_AS_STR(token
), PRIVATE_KEY_STR
) != 0)
446 ret
= DST_R_INVALIDPRIVATEKEY
;
450 NEXTTOKEN(lex
, opt
, &token
);
451 if (token
.type
!= isc_tokentype_string
||
452 (DST_AS_STR(token
))[0] != 'v')
454 ret
= DST_R_INVALIDPRIVATEKEY
;
457 if (sscanf(DST_AS_STR(token
), "v%d.%d", &major
, &minor
) != 2)
459 ret
= DST_R_INVALIDPRIVATEKEY
;
463 if (major
> DST_MAJOR_VERSION
) {
464 ret
= DST_R_INVALIDPRIVATEKEY
;
469 * Store the private key format version number
471 dst_key_setprivateformat(key
, major
, minor
);
473 READLINE(lex
, opt
, &token
);
476 * Read the algorithm line.
478 NEXTTOKEN(lex
, opt
, &token
);
479 if (token
.type
!= isc_tokentype_string
||
480 strcmp(DST_AS_STR(token
), ALGORITHM_STR
) != 0)
482 ret
= DST_R_INVALIDPRIVATEKEY
;
486 NEXTTOKEN(lex
, opt
| ISC_LEXOPT_NUMBER
, &token
);
487 if (token
.type
!= isc_tokentype_number
||
488 token
.value
.as_ulong
!= (unsigned long) dst_key_alg(key
))
490 ret
= DST_R_INVALIDPRIVATEKEY
;
494 READLINE(lex
, opt
, &token
);
499 for (n
= 0; n
< MAXFIELDS
; n
++) {
503 ret
= isc_lex_gettoken(lex
, opt
, &token
);
504 if (ret
== ISC_R_EOF
)
506 if (ret
!= ISC_R_SUCCESS
)
508 } while (token
.type
== isc_tokentype_eol
);
510 if (token
.type
!= isc_tokentype_string
) {
511 ret
= DST_R_INVALIDPRIVATEKEY
;
515 if (strcmp(DST_AS_STR(token
), "External:") == 0) {
520 /* Numeric metadata */
521 tag
= find_numericdata(DST_AS_STR(token
));
523 INSIST(tag
< NUMERIC_NTAGS
);
525 NEXTTOKEN(lex
, opt
| ISC_LEXOPT_NUMBER
, &token
);
526 if (token
.type
!= isc_tokentype_number
) {
527 ret
= DST_R_INVALIDPRIVATEKEY
;
531 dst_key_setnum(key
, tag
, token
.value
.as_ulong
);
535 /* Timing metadata */
536 tag
= find_timedata(DST_AS_STR(token
));
538 INSIST(tag
< TIMING_NTAGS
);
540 NEXTTOKEN(lex
, opt
, &token
);
541 if (token
.type
!= isc_tokentype_string
) {
542 ret
= DST_R_INVALIDPRIVATEKEY
;
546 ret
= dns_time32_fromtext(DST_AS_STR(token
), &when
);
547 if (ret
!= ISC_R_SUCCESS
)
550 dst_key_settime(key
, tag
, when
);
556 tag
= find_value(DST_AS_STR(token
), alg
);
557 if (tag
< 0 && minor
> DST_MINOR_VERSION
)
560 ret
= DST_R_INVALIDPRIVATEKEY
;
564 priv
->elements
[n
].tag
= tag
;
566 data
= (unsigned char *) isc_mem_get(mctx
, MAXFIELDSIZE
);
570 isc_buffer_init(&b
, data
, MAXFIELDSIZE
);
571 ret
= isc_base64_tobuffer(lex
, &b
, -1);
572 if (ret
!= ISC_R_SUCCESS
)
575 isc_buffer_usedregion(&b
, &r
);
576 priv
->elements
[n
].length
= r
.length
;
577 priv
->elements
[n
].data
= r
.base
;
581 READLINE(lex
, opt
, &token
);
586 if (external
&& priv
->nelements
!= 0) {
587 ret
= DST_R_INVALIDPRIVATEKEY
;
591 check
= check_data(priv
, alg
, ISC_TRUE
, external
);
593 ret
= DST_R_INVALIDPRIVATEKEY
;
595 } else if (check
!= ISC_R_SUCCESS
) {
600 key
->external
= external
;
602 return (ISC_R_SUCCESS
);
605 dst__privstruct_free(priv
, mctx
);
607 isc_mem_put(mctx
, data
, MAXFIELDSIZE
);
613 dst__privstruct_writefile(const dst_key_t
*key
, const dst_private_t
*priv
,
614 const char *directory
)
618 char filename
[ISC_DIR_NAMEMAX
];
619 char buffer
[MAXFIELDSIZE
* 2];
620 isc_fsaccess_t access
;
629 REQUIRE(priv
!= NULL
);
631 ret
= check_data(priv
, dst_key_alg(key
), ISC_FALSE
, key
->external
);
633 return (DST_R_INVALIDPRIVATEKEY
);
634 else if (ret
!= ISC_R_SUCCESS
)
637 isc_buffer_init(&b
, filename
, sizeof(filename
));
638 result
= dst_key_buildfilename(key
, DST_TYPE_PRIVATE
, directory
, &b
);
639 if (result
!= ISC_R_SUCCESS
)
642 result
= isc_file_mode(filename
, &mode
);
643 if (result
== ISC_R_SUCCESS
&& mode
!= 0600) {
644 /* File exists; warn that we are changing its permissions */
645 isc_log_write(dns_lctx
, DNS_LOGCATEGORY_GENERAL
,
646 DNS_LOGMODULE_DNSSEC
, ISC_LOG_WARNING
,
647 "Permissions on the file %s "
648 "have changed from 0%o to 0600 as "
649 "a result of this operation.",
650 filename
, (unsigned int)mode
);
653 if ((fp
= fopen(filename
, "w")) == NULL
)
654 return (DST_R_WRITEERROR
);
657 isc_fsaccess_add(ISC_FSACCESS_OWNER
,
658 ISC_FSACCESS_READ
| ISC_FSACCESS_WRITE
,
660 (void)isc_fsaccess_set(filename
, access
);
662 dst_key_getprivateformat(key
, &major
, &minor
);
663 if (major
== 0 && minor
== 0) {
664 major
= DST_MAJOR_VERSION
;
665 minor
= DST_MINOR_VERSION
;
668 /* XXXDCL return value should be checked for full filesystem */
669 fprintf(fp
, "%s v%d.%d\n", PRIVATE_KEY_STR
, major
, minor
);
671 fprintf(fp
, "%s %d ", ALGORITHM_STR
, dst_key_alg(key
));
673 /* XXXVIX this switch statement is too sparse to gen a jump table. */
674 switch (dst_key_alg(key
)) {
676 fprintf(fp
, "(RSA)\n");
679 fprintf(fp
, "(DH)\n");
682 fprintf(fp
, "(DSA)\n");
684 case DST_ALG_RSASHA1
:
685 fprintf(fp
, "(RSASHA1)\n");
687 case DST_ALG_NSEC3RSASHA1
:
688 fprintf(fp
, "(NSEC3RSASHA1)\n");
690 case DST_ALG_NSEC3DSA
:
691 fprintf(fp
, "(NSEC3DSA)\n");
693 case DST_ALG_RSASHA256
:
694 fprintf(fp
, "(RSASHA256)\n");
696 case DST_ALG_RSASHA512
:
697 fprintf(fp
, "(RSASHA512)\n");
699 case DST_ALG_ECCGOST
:
700 fprintf(fp
, "(ECC-GOST)\n");
702 case DST_ALG_ECDSA256
:
703 fprintf(fp
, "(ECDSAP256SHA256)\n");
705 case DST_ALG_ECDSA384
:
706 fprintf(fp
, "(ECDSAP384SHA384)\n");
708 case DST_ALG_HMACMD5
:
709 fprintf(fp
, "(HMAC_MD5)\n");
711 case DST_ALG_HMACSHA1
:
712 fprintf(fp
, "(HMAC_SHA1)\n");
714 case DST_ALG_HMACSHA224
:
715 fprintf(fp
, "(HMAC_SHA224)\n");
717 case DST_ALG_HMACSHA256
:
718 fprintf(fp
, "(HMAC_SHA256)\n");
720 case DST_ALG_HMACSHA384
:
721 fprintf(fp
, "(HMAC_SHA384)\n");
723 case DST_ALG_HMACSHA512
:
724 fprintf(fp
, "(HMAC_SHA512)\n");
727 fprintf(fp
, "(?)\n");
731 for (i
= 0; i
< priv
->nelements
; i
++) {
734 s
= find_tag(priv
->elements
[i
].tag
);
736 r
.base
= priv
->elements
[i
].data
;
737 r
.length
= priv
->elements
[i
].length
;
738 isc_buffer_init(&b
, buffer
, sizeof(buffer
));
739 result
= isc_base64_totext(&r
, sizeof(buffer
), "", &b
);
740 if (result
!= ISC_R_SUCCESS
) {
742 return (DST_R_INVALIDPRIVATEKEY
);
744 isc_buffer_usedregion(&b
, &r
);
746 fprintf(fp
, "%s %.*s\n", s
, (int)r
.length
, r
.base
);
750 fprintf(fp
, "External:\n");
752 /* Add the metadata tags */
753 if (major
> 1 || (major
== 1 && minor
>= 3)) {
754 for (i
= 0; i
< NUMERIC_NTAGS
; i
++) {
755 result
= dst_key_getnum(key
, i
, &value
);
756 if (result
!= ISC_R_SUCCESS
)
758 fprintf(fp
, "%s %u\n", numerictags
[i
], value
);
760 for (i
= 0; i
< TIMING_NTAGS
; i
++) {
761 result
= dst_key_gettime(key
, i
, &when
);
762 if (result
!= ISC_R_SUCCESS
)
765 isc_buffer_init(&b
, buffer
, sizeof(buffer
));
766 result
= dns_time32_totext(when
, &b
);
767 if (result
!= ISC_R_SUCCESS
) {
769 return (DST_R_INVALIDPRIVATEKEY
);
772 isc_buffer_usedregion(&b
, &r
);
774 fprintf(fp
, "%s %.*s\n", timetags
[i
], (int)r
.length
,
780 result
= ferror(fp
) ? DST_R_WRITEERROR
: ISC_R_SUCCESS
;