]> CyberLeo.Net >> Repos - FreeBSD/releng/9.0.git/blob - contrib/bind9/lib/dns/dst_parse.c
Copy stable/9 to releng/9.0 as part of the FreeBSD 9.0-RELEASE release
[FreeBSD/releng/9.0.git] / contrib / bind9 / lib / dns / dst_parse.c
1 /*
2  * Portions Copyright (C) 2004-2010  Internet Systems Consortium, Inc. ("ISC")
3  * Portions Copyright (C) 1999-2002  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 AND NETWORK ASSOCIATES DISCLAIMS
10  * ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED
11  * WARRANTIES OF MERCHANTABILITY AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE
12  * FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
13  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
14  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR
15  * IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
16  *
17  * Portions Copyright (C) 1995-2000 by Network Associates, Inc.
18  *
19  * Permission to use, copy, modify, and/or distribute this software for any
20  * purpose with or without fee is hereby granted, provided that the above
21  * copyright notice and this permission notice appear in all copies.
22  *
23  * THE SOFTWARE IS PROVIDED "AS IS" AND ISC AND NETWORK ASSOCIATES DISCLAIMS
24  * ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED
25  * WARRANTIES OF MERCHANTABILITY AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE
26  * FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
27  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
28  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR
29  * IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
30  */
31
32 /*%
33  * Principal Author: Brian Wellington
34  * $Id: dst_parse.c,v 1.27 2010-12-23 04:07:58 marka Exp $
35  */
36
37 #include <config.h>
38
39 #include <isc/base64.h>
40 #include <isc/dir.h>
41 #include <isc/fsaccess.h>
42 #include <isc/lex.h>
43 #include <isc/mem.h>
44 #include <isc/stdtime.h>
45 #include <isc/string.h>
46 #include <isc/util.h>
47
48 #include <dns/time.h>
49
50 #include "dst_internal.h"
51 #include "dst_parse.h"
52 #include "dst/result.h"
53
54 #define DST_AS_STR(t) ((t).value.as_textregion.base)
55
56 #define PRIVATE_KEY_STR "Private-key-format:"
57 #define ALGORITHM_STR "Algorithm:"
58
59 #define TIMING_NTAGS (DST_MAX_TIMES + 1)
60 static const char *timetags[TIMING_NTAGS] = {
61         "Created:",
62         "Publish:",
63         "Activate:",
64         "Revoke:",
65         "Inactive:",
66         "Delete:",
67         "DSPublish:"
68 };
69
70 #define NUMERIC_NTAGS (DST_MAX_NUMERIC + 1)
71 static const char *numerictags[NUMERIC_NTAGS] = {
72         "Predecessor:",
73         "Successor:",
74         "MaxTTL:",
75         "RollPeriod:"
76 };
77
78 struct parse_map {
79         const int value;
80         const char *tag;
81 };
82
83 static struct parse_map map[] = {
84         {TAG_RSA_MODULUS, "Modulus:"},
85         {TAG_RSA_PUBLICEXPONENT, "PublicExponent:"},
86         {TAG_RSA_PRIVATEEXPONENT, "PrivateExponent:"},
87         {TAG_RSA_PRIME1, "Prime1:"},
88         {TAG_RSA_PRIME2, "Prime2:"},
89         {TAG_RSA_EXPONENT1, "Exponent1:"},
90         {TAG_RSA_EXPONENT2, "Exponent2:"},
91         {TAG_RSA_COEFFICIENT, "Coefficient:"},
92         {TAG_RSA_ENGINE, "Engine:" },
93         {TAG_RSA_LABEL, "Label:" },
94         {TAG_RSA_PIN, "PIN:" },
95
96         {TAG_DH_PRIME, "Prime(p):"},
97         {TAG_DH_GENERATOR, "Generator(g):"},
98         {TAG_DH_PRIVATE, "Private_value(x):"},
99         {TAG_DH_PUBLIC, "Public_value(y):"},
100
101         {TAG_DSA_PRIME, "Prime(p):"},
102         {TAG_DSA_SUBPRIME, "Subprime(q):"},
103         {TAG_DSA_BASE, "Base(g):"},
104         {TAG_DSA_PRIVATE, "Private_value(x):"},
105         {TAG_DSA_PUBLIC, "Public_value(y):"},
106
107         {TAG_GOST_PRIVASN1, "GostAsn1:"},
108
109         {TAG_HMACMD5_KEY, "Key:"},
110         {TAG_HMACMD5_BITS, "Bits:"},
111
112         {TAG_HMACSHA1_KEY, "Key:"},
113         {TAG_HMACSHA1_BITS, "Bits:"},
114
115         {TAG_HMACSHA224_KEY, "Key:"},
116         {TAG_HMACSHA224_BITS, "Bits:"},
117
118         {TAG_HMACSHA256_KEY, "Key:"},
119         {TAG_HMACSHA256_BITS, "Bits:"},
120
121         {TAG_HMACSHA384_KEY, "Key:"},
122         {TAG_HMACSHA384_BITS, "Bits:"},
123
124         {TAG_HMACSHA512_KEY, "Key:"},
125         {TAG_HMACSHA512_BITS, "Bits:"},
126
127         {0, NULL}
128 };
129
130 static int
131 find_value(const char *s, const unsigned int alg) {
132         int i;
133
134         for (i = 0; map[i].tag != NULL; i++) {
135                 if (strcasecmp(s, map[i].tag) == 0 &&
136                     (TAG_ALG(map[i].value) == alg))
137                         return (map[i].value);
138         }
139         return (-1);
140 }
141
142 static const char *
143 find_tag(const int value) {
144         int i;
145
146         for (i = 0; ; i++) {
147                 if (map[i].tag == NULL)
148                         return (NULL);
149                 else if (value == map[i].value)
150                         return (map[i].tag);
151         }
152 }
153
154 static int
155 find_metadata(const char *s, const char *tags[], int ntags) {
156         int i;
157
158         for (i = 0; i < ntags; i++) {
159                 if (strcasecmp(s, tags[i]) == 0)
160                         return (i);
161         }
162
163         return (-1);
164 }
165
166 static int
167 find_timedata(const char *s) {
168         return (find_metadata(s, timetags, TIMING_NTAGS));
169 }
170
171 static int
172 find_numericdata(const char *s) {
173         return (find_metadata(s, numerictags, NUMERIC_NTAGS));
174 }
175
176 static int
177 check_rsa(const dst_private_t *priv) {
178         int i, j;
179         isc_boolean_t have[RSA_NTAGS];
180         isc_boolean_t ok;
181         unsigned int mask;
182
183         for (i = 0; i < RSA_NTAGS; i++)
184                 have[i] = ISC_FALSE;
185         for (j = 0; j < priv->nelements; j++) {
186                 for (i = 0; i < RSA_NTAGS; i++)
187                         if (priv->elements[j].tag == TAG(DST_ALG_RSAMD5, i))
188                                 break;
189                 if (i == RSA_NTAGS)
190                         return (-1);
191                 have[i] = ISC_TRUE;
192         }
193
194         mask = ~0;
195         mask <<= sizeof(mask) * 8 - TAG_SHIFT;
196         mask >>= sizeof(mask) * 8 - TAG_SHIFT;
197
198         if (have[TAG_RSA_ENGINE & mask])
199                 ok = have[TAG_RSA_MODULUS & mask] &&
200                      have[TAG_RSA_PUBLICEXPONENT & mask] &&
201                      have[TAG_RSA_LABEL & mask];
202         else
203                 ok = have[TAG_RSA_MODULUS & mask] &&
204                      have[TAG_RSA_PUBLICEXPONENT & mask] &&
205                      have[TAG_RSA_PRIVATEEXPONENT & mask] &&
206                      have[TAG_RSA_PRIME1 & mask] &&
207                      have[TAG_RSA_PRIME2 & mask] &&
208                      have[TAG_RSA_EXPONENT1 & mask] &&
209                      have[TAG_RSA_EXPONENT2 & mask] &&
210                      have[TAG_RSA_COEFFICIENT & mask];
211         return (ok ? 0 : -1 );
212 }
213
214 static int
215 check_dh(const dst_private_t *priv) {
216         int i, j;
217         if (priv->nelements != DH_NTAGS)
218                 return (-1);
219         for (i = 0; i < DH_NTAGS; i++) {
220                 for (j = 0; j < priv->nelements; j++)
221                         if (priv->elements[j].tag == TAG(DST_ALG_DH, i))
222                                 break;
223                 if (j == priv->nelements)
224                         return (-1);
225         }
226         return (0);
227 }
228
229 static int
230 check_dsa(const dst_private_t *priv) {
231         int i, j;
232         if (priv->nelements != DSA_NTAGS)
233                 return (-1);
234         for (i = 0; i < DSA_NTAGS; i++) {
235                 for (j = 0; j < priv->nelements; j++)
236                         if (priv->elements[j].tag == TAG(DST_ALG_DSA, i))
237                                 break;
238                 if (j == priv->nelements)
239                         return (-1);
240         }
241         return (0);
242 }
243
244 static int
245 check_gost(const dst_private_t *priv) {
246         if (priv->nelements != GOST_NTAGS)
247                 return (-1);
248         if (priv->elements[0].tag != TAG(DST_ALG_ECCGOST, 0))
249                 return (-1);
250         return (0);
251 }
252
253 static int
254 check_hmac_md5(const dst_private_t *priv, isc_boolean_t old) {
255         int i, j;
256
257         if (priv->nelements != HMACMD5_NTAGS) {
258                 /*
259                  * If this is a good old format and we are accepting
260                  * the old format return success.
261                  */
262                 if (old && priv->nelements == OLD_HMACMD5_NTAGS &&
263                     priv->elements[0].tag == TAG_HMACMD5_KEY)
264                         return (0);
265                 return (-1);
266         }
267         /*
268          * We must be new format at this point.
269          */
270         for (i = 0; i < HMACMD5_NTAGS; i++) {
271                 for (j = 0; j < priv->nelements; j++)
272                         if (priv->elements[j].tag == TAG(DST_ALG_HMACMD5, i))
273                                 break;
274                 if (j == priv->nelements)
275                         return (-1);
276         }
277         return (0);
278 }
279
280 static int
281 check_hmac_sha(const dst_private_t *priv, unsigned int ntags,
282                unsigned int alg)
283 {
284         unsigned int i, j;
285         if (priv->nelements != ntags)
286                 return (-1);
287         for (i = 0; i < ntags; i++) {
288                 for (j = 0; j < priv->nelements; j++)
289                         if (priv->elements[j].tag == TAG(alg, i))
290                                 break;
291                 if (j == priv->nelements)
292                         return (-1);
293         }
294         return (0);
295 }
296
297 static int
298 check_data(const dst_private_t *priv, const unsigned int alg,
299            isc_boolean_t old)
300 {
301         /* XXXVIX this switch statement is too sparse to gen a jump table. */
302         switch (alg) {
303         case DST_ALG_RSAMD5:
304         case DST_ALG_RSASHA1:
305                 return (check_rsa(priv));
306         case DST_ALG_DH:
307                 return (check_dh(priv));
308         case DST_ALG_DSA:
309                 return (check_dsa(priv));
310         case DST_ALG_ECCGOST:
311                 return (check_gost(priv));
312         case DST_ALG_HMACMD5:
313                 return (check_hmac_md5(priv, old));
314         case DST_ALG_HMACSHA1:
315                 return (check_hmac_sha(priv, HMACSHA1_NTAGS, alg));
316         case DST_ALG_HMACSHA224:
317                 return (check_hmac_sha(priv, HMACSHA224_NTAGS, alg));
318         case DST_ALG_HMACSHA256:
319                 return (check_hmac_sha(priv, HMACSHA256_NTAGS, alg));
320         case DST_ALG_HMACSHA384:
321                 return (check_hmac_sha(priv, HMACSHA384_NTAGS, alg));
322         case DST_ALG_HMACSHA512:
323                 return (check_hmac_sha(priv, HMACSHA512_NTAGS, alg));
324         default:
325                 return (DST_R_UNSUPPORTEDALG);
326         }
327 }
328
329 void
330 dst__privstruct_free(dst_private_t *priv, isc_mem_t *mctx) {
331         int i;
332
333         if (priv == NULL)
334                 return;
335         for (i = 0; i < priv->nelements; i++) {
336                 if (priv->elements[i].data == NULL)
337                         continue;
338                 memset(priv->elements[i].data, 0, MAXFIELDSIZE);
339                 isc_mem_put(mctx, priv->elements[i].data, MAXFIELDSIZE);
340         }
341         priv->nelements = 0;
342 }
343
344 isc_result_t
345 dst__privstruct_parse(dst_key_t *key, unsigned int alg, isc_lex_t *lex,
346                       isc_mem_t *mctx, dst_private_t *priv)
347 {
348         int n = 0, major, minor;
349         isc_buffer_t b;
350         isc_token_t token;
351         unsigned char *data = NULL;
352         unsigned int opt = ISC_LEXOPT_EOL;
353         isc_stdtime_t when;
354         isc_result_t ret;
355
356         REQUIRE(priv != NULL);
357
358         priv->nelements = 0;
359         memset(priv->elements, 0, sizeof(priv->elements));
360
361 #define NEXTTOKEN(lex, opt, token)                              \
362         do {                                                    \
363                 ret = isc_lex_gettoken(lex, opt, token);        \
364                 if (ret != ISC_R_SUCCESS)                       \
365                         goto fail;                              \
366         } while (0)
367
368 #define READLINE(lex, opt, token)                               \
369         do {                                                    \
370                 ret = isc_lex_gettoken(lex, opt, token);        \
371                 if (ret == ISC_R_EOF)                           \
372                         break;                                  \
373                 else if (ret != ISC_R_SUCCESS)                  \
374                         goto fail;                              \
375         } while ((*token).type != isc_tokentype_eol)
376
377         /*
378          * Read the description line.
379          */
380         NEXTTOKEN(lex, opt, &token);
381         if (token.type != isc_tokentype_string ||
382             strcmp(DST_AS_STR(token), PRIVATE_KEY_STR) != 0)
383         {
384                 ret = DST_R_INVALIDPRIVATEKEY;
385                 goto fail;
386         }
387
388         NEXTTOKEN(lex, opt, &token);
389         if (token.type != isc_tokentype_string ||
390             (DST_AS_STR(token))[0] != 'v')
391         {
392                 ret = DST_R_INVALIDPRIVATEKEY;
393                 goto fail;
394         }
395         if (sscanf(DST_AS_STR(token), "v%d.%d", &major, &minor) != 2)
396         {
397                 ret = DST_R_INVALIDPRIVATEKEY;
398                 goto fail;
399         }
400
401         if (major > DST_MAJOR_VERSION) {
402                 ret = DST_R_INVALIDPRIVATEKEY;
403                 goto fail;
404         }
405
406         /*
407          * Store the private key format version number
408          */
409         dst_key_setprivateformat(key, major, minor);
410
411         READLINE(lex, opt, &token);
412
413         /*
414          * Read the algorithm line.
415          */
416         NEXTTOKEN(lex, opt, &token);
417         if (token.type != isc_tokentype_string ||
418             strcmp(DST_AS_STR(token), ALGORITHM_STR) != 0)
419         {
420                 ret = DST_R_INVALIDPRIVATEKEY;
421                 goto fail;
422         }
423
424         NEXTTOKEN(lex, opt | ISC_LEXOPT_NUMBER, &token);
425         if (token.type != isc_tokentype_number ||
426             token.value.as_ulong != (unsigned long) dst_key_alg(key))
427         {
428                 ret = DST_R_INVALIDPRIVATEKEY;
429                 goto fail;
430         }
431
432         READLINE(lex, opt, &token);
433
434         /*
435          * Read the key data.
436          */
437         for (n = 0; n < MAXFIELDS; n++) {
438                 int tag;
439                 isc_region_t r;
440                 do {
441                         ret = isc_lex_gettoken(lex, opt, &token);
442                         if (ret == ISC_R_EOF)
443                                 goto done;
444                         if (ret != ISC_R_SUCCESS)
445                                 goto fail;
446                 } while (token.type == isc_tokentype_eol);
447
448                 if (token.type != isc_tokentype_string) {
449                         ret = DST_R_INVALIDPRIVATEKEY;
450                         goto fail;
451                 }
452
453                 /* Numeric metadata */
454                 tag = find_numericdata(DST_AS_STR(token));
455                 if (tag >= 0) {
456                         INSIST(tag < NUMERIC_NTAGS);
457
458                         NEXTTOKEN(lex, opt | ISC_LEXOPT_NUMBER, &token);
459                         if (token.type != isc_tokentype_number) {
460                                 ret = DST_R_INVALIDPRIVATEKEY;
461                                 goto fail;
462                         }
463
464                         dst_key_setnum(key, tag, token.value.as_ulong);
465                         goto next;
466                 }
467
468                 /* Timing metadata */
469                 tag = find_timedata(DST_AS_STR(token));
470                 if (tag >= 0) {
471                         INSIST(tag < TIMING_NTAGS);
472
473                         NEXTTOKEN(lex, opt, &token);
474                         if (token.type != isc_tokentype_string) {
475                                 ret = DST_R_INVALIDPRIVATEKEY;
476                                 goto fail;
477                         }
478
479                         ret = dns_time32_fromtext(DST_AS_STR(token), &when);
480                         if (ret != ISC_R_SUCCESS)
481                                 goto fail;
482
483                         dst_key_settime(key, tag, when);
484
485                         goto next;
486                 }
487
488                 /* Key data */
489                 tag = find_value(DST_AS_STR(token), alg);
490                 if (tag < 0 && minor > DST_MINOR_VERSION)
491                         goto next;
492                 else if (tag < 0) {
493                         ret = DST_R_INVALIDPRIVATEKEY;
494                         goto fail;
495                 }
496
497                 priv->elements[n].tag = tag;
498
499                 data = (unsigned char *) isc_mem_get(mctx, MAXFIELDSIZE);
500                 if (data == NULL)
501                         goto fail;
502
503                 isc_buffer_init(&b, data, MAXFIELDSIZE);
504                 ret = isc_base64_tobuffer(lex, &b, -1);
505                 if (ret != ISC_R_SUCCESS)
506                         goto fail;
507
508                 isc_buffer_usedregion(&b, &r);
509                 priv->elements[n].length = r.length;
510                 priv->elements[n].data = r.base;
511                 priv->nelements++;
512
513           next:
514                 READLINE(lex, opt, &token);
515                 data = NULL;
516         }
517  done:
518         if (check_data(priv, alg, ISC_TRUE) < 0)
519                 goto fail;
520
521         return (ISC_R_SUCCESS);
522
523 fail:
524         dst__privstruct_free(priv, mctx);
525         if (data != NULL)
526                 isc_mem_put(mctx, data, MAXFIELDSIZE);
527
528         return (ret);
529 }
530
531 isc_result_t
532 dst__privstruct_writefile(const dst_key_t *key, const dst_private_t *priv,
533                           const char *directory)
534 {
535         FILE *fp;
536         int ret, i;
537         isc_result_t result;
538         char filename[ISC_DIR_NAMEMAX];
539         char buffer[MAXFIELDSIZE * 2];
540         isc_fsaccess_t access;
541         isc_stdtime_t when;
542         isc_uint32_t value;
543         isc_buffer_t b;
544         isc_region_t r;
545         int major, minor;
546
547         REQUIRE(priv != NULL);
548
549         if (check_data(priv, dst_key_alg(key), ISC_FALSE) < 0)
550                 return (DST_R_INVALIDPRIVATEKEY);
551
552         isc_buffer_init(&b, filename, sizeof(filename));
553         ret = dst_key_buildfilename(key, DST_TYPE_PRIVATE, directory, &b);
554         if (ret != ISC_R_SUCCESS)
555                 return (ret);
556
557         if ((fp = fopen(filename, "w")) == NULL)
558                 return (DST_R_WRITEERROR);
559
560         access = 0;
561         isc_fsaccess_add(ISC_FSACCESS_OWNER,
562                          ISC_FSACCESS_READ | ISC_FSACCESS_WRITE,
563                          &access);
564         (void)isc_fsaccess_set(filename, access);
565
566         dst_key_getprivateformat(key, &major, &minor);
567         if (major == 0 && minor == 0) {
568                 major = DST_MAJOR_VERSION;
569                 minor = DST_MINOR_VERSION;
570         }
571
572         /* XXXDCL return value should be checked for full filesystem */
573         fprintf(fp, "%s v%d.%d\n", PRIVATE_KEY_STR, major, minor);
574
575         fprintf(fp, "%s %d ", ALGORITHM_STR, dst_key_alg(key));
576
577         /* XXXVIX this switch statement is too sparse to gen a jump table. */
578         switch (dst_key_alg(key)) {
579         case DST_ALG_RSAMD5:
580                 fprintf(fp, "(RSA)\n");
581                 break;
582         case DST_ALG_DH:
583                 fprintf(fp, "(DH)\n");
584                 break;
585         case DST_ALG_DSA:
586                 fprintf(fp, "(DSA)\n");
587                 break;
588         case DST_ALG_RSASHA1:
589                 fprintf(fp, "(RSASHA1)\n");
590                 break;
591         case DST_ALG_NSEC3RSASHA1:
592                 fprintf(fp, "(NSEC3RSASHA1)\n");
593                 break;
594         case DST_ALG_NSEC3DSA:
595                 fprintf(fp, "(NSEC3DSA)\n");
596                 break;
597         case DST_ALG_RSASHA256:
598                 fprintf(fp, "(RSASHA256)\n");
599                 break;
600         case DST_ALG_RSASHA512:
601                 fprintf(fp, "(RSASHA512)\n");
602                 break;
603         case DST_ALG_ECCGOST:
604                 fprintf(fp, "(ECC-GOST)\n");
605                 break;
606         case DST_ALG_HMACMD5:
607                 fprintf(fp, "(HMAC_MD5)\n");
608                 break;
609         case DST_ALG_HMACSHA1:
610                 fprintf(fp, "(HMAC_SHA1)\n");
611                 break;
612         case DST_ALG_HMACSHA224:
613                 fprintf(fp, "(HMAC_SHA224)\n");
614                 break;
615         case DST_ALG_HMACSHA256:
616                 fprintf(fp, "(HMAC_SHA256)\n");
617                 break;
618         case DST_ALG_HMACSHA384:
619                 fprintf(fp, "(HMAC_SHA384)\n");
620                 break;
621         case DST_ALG_HMACSHA512:
622                 fprintf(fp, "(HMAC_SHA512)\n");
623                 break;
624         default:
625                 fprintf(fp, "(?)\n");
626                 break;
627         }
628
629         for (i = 0; i < priv->nelements; i++) {
630                 const char *s;
631
632                 s = find_tag(priv->elements[i].tag);
633
634                 r.base = priv->elements[i].data;
635                 r.length = priv->elements[i].length;
636                 isc_buffer_init(&b, buffer, sizeof(buffer));
637                 result = isc_base64_totext(&r, sizeof(buffer), "", &b);
638                 if (result != ISC_R_SUCCESS) {
639                         fclose(fp);
640                         return (DST_R_INVALIDPRIVATEKEY);
641                 }
642                 isc_buffer_usedregion(&b, &r);
643
644                 fprintf(fp, "%s ", s);
645                 isc_util_fwrite(r.base, 1, r.length, fp);
646                 fprintf(fp, "\n");
647         }
648
649         /* Add the metadata tags */
650         if (major > 1 || (major == 1 && minor >= 3)) {
651                 for (i = 0; i < NUMERIC_NTAGS; i++) {
652                         result = dst_key_getnum(key, i, &value);
653                         if (result != ISC_R_SUCCESS)
654                                 continue;
655                         fprintf(fp, "%s %u\n", numerictags[i], value);
656                 }
657                 for (i = 0; i < TIMING_NTAGS; i++) {
658                         result = dst_key_gettime(key, i, &when);
659                         if (result != ISC_R_SUCCESS)
660                                 continue;
661
662                         isc_buffer_init(&b, buffer, sizeof(buffer));
663                         result = dns_time32_totext(when, &b);
664                         if (result != ISC_R_SUCCESS)
665                                 continue;
666
667                         isc_buffer_usedregion(&b, &r);
668
669                         fprintf(fp, "%s ", timetags[i]);
670                         isc_util_fwrite(r.base, 1, r.length, fp);
671                         fprintf(fp, "\n");
672                 }
673         }
674
675         fflush(fp);
676         result = ferror(fp) ? DST_R_WRITEERROR : ISC_R_SUCCESS;
677         fclose(fp);
678         return (result);
679 }
680
681 /*! \file */