1/* 2 * Portions Copyright (C) 2004-2012 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$ 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) 60static 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) 71static const char *numerictags[NUMERIC_NTAGS] = { 72 "Predecessor:", 73 "Successor:", 74 "MaxTTL:", 75 "RollPeriod:" 76}; 77 78struct parse_map { 79 const int value; 80 const char *tag; 81}; 82 83static 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 130static int 131find_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 142static const char * 143find_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 154static int 155find_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 166static int 167find_timedata(const char *s) { 168 return (find_metadata(s, timetags, TIMING_NTAGS)); 169} 170 171static int 172find_numericdata(const char *s) { 173 return (find_metadata(s, numerictags, NUMERIC_NTAGS)); 174} 175 176static int 177check_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 214static int 215check_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 229static int 230check_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 244static int 245check_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 253static int 254check_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 280static int 281check_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 297static int 298check_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 329void 330dst__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 344isc_result_t 345dst__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 523fail: 524 dst__privstruct_free(priv, mctx); 525 if (data != NULL) 526 isc_mem_put(mctx, data, MAXFIELDSIZE); 527 528 return (ret); 529} 530 531isc_result_t 532dst__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\n", s, (int)r.length, r.base); 645 } 646 647 /* Add the metadata tags */ 648 if (major > 1 || (major == 1 && minor >= 3)) { 649 for (i = 0; i < NUMERIC_NTAGS; i++) { 650 result = dst_key_getnum(key, i, &value); 651 if (result != ISC_R_SUCCESS) 652 continue; 653 fprintf(fp, "%s %u\n", numerictags[i], value); 654 } 655 for (i = 0; i < TIMING_NTAGS; i++) { 656 result = dst_key_gettime(key, i, &when); 657 if (result != ISC_R_SUCCESS) 658 continue; 659 660 isc_buffer_init(&b, buffer, sizeof(buffer)); 661 result = dns_time32_totext(when, &b); 662 if (result != ISC_R_SUCCESS) { 663 fclose(fp); 664 return (DST_R_INVALIDPRIVATEKEY); 665 } 666 667 isc_buffer_usedregion(&b, &r); 668 669 fprintf(fp, "%s %.*s\n", timetags[i], (int)r.length, 670 r.base); 671 } 672 } 673 674 fflush(fp); 675 result = ferror(fp) ? DST_R_WRITEERROR : ISC_R_SUCCESS; 676 fclose(fp); 677 return (result); 678} 679 680/*! \file */ 681