1 /* 2 * Copyright (c) 2011 Alex Hornung <alex@alexhornung.com>. 3 * All rights reserved. 4 * 5 * Redistribution and use in source and binary forms, with or without 6 * modification, are permitted provided that the following conditions 7 * are met: 8 * 9 * 1. Redistributions of source code must retain the above copyright 10 * notice, this list of conditions and the following disclaimer. 11 * 2. Redistributions in binary form must reproduce the above copyright 12 * notice, this list of conditions and the following disclaimer in 13 * the documentation and/or other materials provided with the 14 * distribution. 15 * 16 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 17 * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 18 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS 19 * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE 20 * COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, 21 * INCIDENTAL, SPECIAL, EXEMPLARY OR CONSEQUENTIAL DAMAGES (INCLUDING, 22 * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 23 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED 24 * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 25 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT 26 * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 27 * SUCH DAMAGE. 28 */ 29 30 #include <stdlib.h> 31 #include <unistd.h> 32 #include <errno.h> 33 #include <string.h> 34 #include <stdio.h> 35 #include <stdarg.h> 36 37 #include "tcplay.h" 38 #include "tcplay_api.h" 39 #include "tcplay_api_internal.h" 40 41 42 int 43 tc_api_init(int verbose) 44 { 45 int error; 46 47 tc_internal_verbose = verbose; 48 49 if ((error = tc_play_init()) != 0) 50 return TC_ERR; 51 else 52 return TC_OK; 53 } 54 55 int 56 tc_api_uninit(void) 57 { 58 check_and_purge_safe_mem(); 59 return TC_OK; 60 } 61 62 63 static const char *_caps[] = { 64 "trim", 65 NULL 66 }; 67 68 int 69 tc_api_has(const char *feature) 70 { 71 const char *cap; 72 int i; 73 74 for (cap = _caps[0], i = 0; cap != NULL; cap = _caps[++i]) { 75 if ((strcmp(cap, feature)) == 0) 76 return TC_OK; 77 } 78 79 return TC_ERR_UNIMPL; 80 } 81 82 int 83 tc_api_cipher_iterate(tc_api_cipher_iterator_fn fn, void *priv) 84 { 85 int i; 86 struct tc_cipher_chain *chain; 87 int klen; 88 int length; 89 char buf[1024]; 90 91 if (fn == NULL) { 92 errno = EFAULT; 93 return TC_ERR; 94 } 95 96 for (i = 0, chain = tc_cipher_chains[0]; chain != NULL; 97 chain = tc_cipher_chains[++i]) { 98 tc_cipher_chain_sprint(buf, sizeof(buf), chain); 99 klen = tc_cipher_chain_klen(chain); 100 length = tc_cipher_chain_length(chain); 101 if ((fn(priv, buf, klen, length)) < 0) 102 break; 103 } 104 105 return TC_OK; 106 } 107 108 int 109 tc_api_prf_iterate(tc_api_prf_iterator_fn fn, void *priv) 110 { 111 int i; 112 113 if (fn == NULL) { 114 errno = EFAULT; 115 return TC_ERR; 116 } 117 118 /* start at 1 due to RIPEMD weirdness... */ 119 for (i = 1; pbkdf_prf_algos[i].name != NULL; i++) { 120 if ((fn(priv, pbkdf_prf_algos[i].name)) < 0) 121 break; 122 } 123 124 return TC_OK; 125 } 126 127 128 const char * 129 tc_api_task_get_error(tc_api_task task __unused) 130 { 131 return tc_internal_log_buffer; 132 } 133 134 135 #define _match(k, v) (strcmp(k, v) == 0) 136 137 tc_api_task 138 tc_api_task_init(const char *op) 139 { 140 tc_api_task task = NULL; 141 int fail = 1; 142 143 if ((task = alloc_safe_mem(sizeof(*task))) == NULL) { 144 errno = ENOMEM; 145 goto out; 146 } 147 148 if ((task->opts = opts_init()) == NULL) { 149 errno = ENOMEM; 150 goto out; 151 } 152 153 if (_match(op, "create")) { 154 task->op = TC_OP_CREATE; 155 } else if (_match(op, "map")) { 156 task->op = TC_OP_MAP; 157 } else if (_match(op, "unmap")) { 158 task->op = TC_OP_UNMAP; 159 } else if (_match(op, "info")) { 160 task->op = TC_OP_INFO; 161 } else if (_match(op, "info_mapped")) { 162 task->op = TC_OP_INFO_MAPPED; 163 } else if (_match(op, "modify")) { 164 task->op = TC_OP_MODIFY; 165 } else if (_match(op, "restore")) { 166 task->op = TC_OP_RESTORE; 167 } else { 168 errno = EINVAL; 169 goto out; 170 } 171 172 fail = 0; 173 174 out: 175 if (fail && task != NULL) { 176 if (task->opts != NULL) 177 opts_free(task->opts); 178 free_safe_mem(task); 179 } 180 181 return fail ? NULL : task; 182 } 183 184 int 185 tc_api_task_uninit(tc_api_task task) 186 { 187 if (task->last_info != NULL) 188 free_info(task->last_info); 189 opts_free(task->opts); 190 free_safe_mem(task); 191 192 return TC_OK; 193 } 194 195 196 #define _set_str(k) \ 197 do { \ 198 if ((opts->k = strdup_safe_mem(s)) == NULL) { \ 199 errno = ENOMEM; \ 200 r = TC_ERR; \ 201 goto out; \ 202 } \ 203 } while (0) 204 205 #define _clr_str(k) \ 206 do { \ 207 if (opts->k) \ 208 free_safe_mem(opts->k); \ 209 opts->k = NULL; \ 210 } while (0) 211 212 int 213 tc_api_task_set(tc_api_task task, const char *key, ...) 214 { 215 struct tcplay_opts *opts; 216 va_list ap; 217 const char *s; 218 int64_t i64; 219 int i; 220 tc_api_state_change_fn sc_fn; 221 void *vp; 222 int r = TC_OK; 223 224 if (task == NULL || ((opts = task->opts) == NULL)) { 225 errno = EFAULT; 226 return TC_ERR; 227 } 228 229 va_start(ap, key); 230 231 if (_match(key, "interactive")) { 232 i = va_arg(ap, int); 233 opts->interactive = i; 234 } else if (_match(key, "weak_keys_and_salt")) { 235 i = va_arg(ap, int); 236 opts->weak_keys_and_salt = i; 237 } else if (_match(key, "secure_erase")) { 238 i = va_arg(ap, int); 239 opts->secure_erase = i; 240 } else if (_match(key, "protect_hidden")) { 241 i = va_arg(ap, int); 242 opts->protect_hidden = i; 243 } else if (_match(key, "fde")) { 244 i = va_arg(ap, int); 245 if (i) 246 opts->flags |= TC_FLAG_FDE; 247 else 248 opts->flags &= ~TC_FLAG_FDE; 249 } else if (_match(key, "use_backup_header")) { 250 i = va_arg(ap, int); 251 if (i) 252 opts->flags |= TC_FLAG_BACKUP; 253 else 254 opts->flags &= ~TC_FLAG_BACKUP; 255 } else if (_match(key, "allow_trim")) { 256 i = va_arg(ap, int); 257 if (i) 258 opts->flags |= TC_FLAG_ALLOW_TRIM; 259 else 260 opts->flags &= ~TC_FLAG_ALLOW_TRIM; 261 } else if (_match(key, "hidden_size_bytes")) { 262 i64 = va_arg(ap, int64_t); 263 opts->hidden_size_bytes = (disksz_t)i64; 264 opts->hidden = (i64 > 0); 265 } else if (_match(key, "retries")) { 266 i = va_arg(ap, int); 267 opts->retries = i; 268 } else if (_match(key, "timeout")) { 269 i = va_arg(ap, int); 270 opts->timeout = (time_t)i; 271 } else if (_match(key, "save_header_to_file")) { 272 s = va_arg(ap, const char *); 273 if (s != NULL) { 274 _set_str(hdr_file_out); 275 opts->flags |= TC_FLAG_SAVE_TO_FILE; 276 } else { 277 _clr_str(hdr_file_out); 278 opts->flags &= ~TC_FLAG_SAVE_TO_FILE; 279 } 280 } else if (_match(key, "header_from_file")) { 281 s = va_arg(ap, const char *); 282 if (s != NULL) { 283 _set_str(hdr_file_in); 284 opts->flags |= TC_FLAG_HDR_FROM_FILE; 285 } else { 286 _clr_str(hdr_file_in); 287 opts->flags &= ~TC_FLAG_HDR_FROM_FILE; 288 } 289 } else if (_match(key, "hidden_header_from_file")) { 290 s = va_arg(ap, const char *); 291 if (s != NULL) { 292 _set_str(h_hdr_file_in); 293 opts->flags |= TC_FLAG_H_HDR_FROM_FILE; 294 } else { 295 _clr_str(h_hdr_file_in); 296 opts->flags &= ~TC_FLAG_H_HDR_FROM_FILE; 297 } 298 } else if (_match(key, "sys")) { 299 s = va_arg(ap, const char *); 300 if (s != NULL) { 301 _set_str(sys_dev); 302 opts->flags |= TC_FLAG_SYS; 303 } else { 304 _clr_str(sys_dev); 305 opts->flags &= ~TC_FLAG_SYS; 306 } 307 } else if (_match(key, "passphrase")) { 308 s = va_arg(ap, const char *); 309 if (s != NULL) { 310 _set_str(passphrase); 311 } else { 312 _clr_str(passphrase); 313 } 314 } else if (_match(key, "h_passphrase")) { 315 s = va_arg(ap, const char *); 316 if (s != NULL) { 317 _set_str(h_passphrase); 318 } else { 319 _clr_str(h_passphrase); 320 } 321 } else if (_match(key, "new_passphrase")) { 322 s = va_arg(ap, const char *); 323 if (s != NULL) { 324 _set_str(new_passphrase); 325 } else { 326 _clr_str(new_passphrase); 327 } 328 } else if (_match(key, "dev")) { 329 s = va_arg(ap, const char *); 330 if (s != NULL) { 331 _set_str(dev); 332 } else { 333 _clr_str(dev); 334 } 335 } else if (_match(key, "map_name")) { 336 s = va_arg(ap, const char *); 337 if (s != NULL) { 338 _set_str(map_name); 339 } else { 340 _clr_str(map_name); 341 } 342 } else if (_match(key, "keyfiles")) { 343 s = va_arg(ap, const char *); 344 if (s != NULL) { 345 opts_add_keyfile(opts, s); 346 } else { 347 opts_clear_keyfile(opts); 348 } 349 } else if (_match(key, "h_keyfiles")) { 350 s = va_arg(ap, const char *); 351 if (s != NULL) { 352 opts_add_keyfile_hidden(opts, s); 353 } else { 354 opts_clear_keyfile_hidden(opts); 355 } 356 } else if (_match(key, "new_keyfiles")) { 357 s = va_arg(ap, const char *); 358 if (s != NULL) { 359 opts_add_keyfile_new(opts, s); 360 } else { 361 opts_clear_keyfile_new(opts); 362 } 363 } else if (_match(key, "prf_algo")) { 364 s = va_arg(ap, const char *); 365 if (s != NULL) { 366 if ((opts->prf_algo = check_prf_algo(s, 1)) == NULL) { 367 errno = ENOENT; 368 r = TC_ERR; 369 goto out; 370 } 371 } else { 372 opts->prf_algo = NULL; 373 } 374 } else if (_match(key, "h_prf_algo")) { 375 s = va_arg(ap, const char *); 376 if (s != NULL) { 377 if ((opts->h_prf_algo = check_prf_algo(s, 1)) == NULL) { 378 errno = ENOENT; 379 r = TC_ERR; 380 goto out; 381 } 382 } else { 383 opts->h_prf_algo = NULL; 384 } 385 } else if (_match(key, "new_prf_algo")) { 386 s = va_arg(ap, const char *); 387 if (s != NULL) { 388 if ((opts->new_prf_algo = check_prf_algo(s, 1)) == NULL) { 389 errno = ENOENT; 390 r = TC_ERR; 391 goto out; 392 } 393 } else { 394 opts->new_prf_algo = NULL; 395 } 396 } else if (_match(key, "cipher_chain")) { 397 s = va_arg(ap, const char *); 398 if (s != NULL) { 399 if ((opts->cipher_chain = check_cipher_chain(s, 1)) == NULL) { 400 errno = ENOENT; 401 r = TC_ERR; 402 goto out; 403 } 404 } else { 405 opts->cipher_chain = NULL; 406 } 407 } else if (_match(key, "h_cipher_chain")) { 408 s = va_arg(ap, const char *); 409 if (s != NULL) { 410 if ((opts->h_cipher_chain = check_cipher_chain(s, 1)) == NULL) { 411 errno = ENOENT; 412 r = TC_ERR; 413 goto out; 414 } 415 } else { 416 opts->h_cipher_chain = NULL; 417 } 418 } else if (_match(key, "state_change_fn")) { 419 sc_fn = va_arg(ap, tc_api_state_change_fn); 420 opts->state_change_fn = sc_fn; 421 vp = va_arg(ap, void *); 422 opts->api_ctx = vp; 423 } else { 424 r = TC_ERR_UNIMPL; 425 } 426 427 out: 428 va_end(ap); 429 430 return r; 431 } 432 433 #define _not_null(x) \ 434 if (opts->x == NULL) { \ 435 return -1; \ 436 } 437 438 #define _null(x) \ 439 if (opts->x != NULL) { \ 440 return -1; \ 441 } 442 443 #define _zero(x) \ 444 if (opts->x != 0) { \ 445 return -1; \ 446 } 447 448 #define _not_set(x) \ 449 if (TC_FLAG_SET(opts->flags, x)) { \ 450 return -1; \ 451 } 452 453 static 454 int 455 _opts_check_create(struct tcplay_opts *opts) 456 { 457 _not_null(dev); 458 _not_set(SYS); 459 _not_set(FDE); 460 _not_set(BACKUP); 461 _not_set(ONLY_RESTORE); 462 _not_set(ALLOW_TRIM); 463 _not_set(SAVE_TO_FILE); 464 _not_set(HDR_FROM_FILE); 465 _not_set(H_HDR_FROM_FILE); 466 467 _null(map_name); 468 _zero(protect_hidden); 469 _null(new_passphrase); 470 _null(new_prf_algo); 471 _zero(n_newkeyfiles); 472 473 if (opts->hidden_size_bytes && !opts->hidden) { 474 return -1; 475 } 476 477 return 0; 478 } 479 480 static 481 int 482 _opts_check_map(struct tcplay_opts *opts) 483 { 484 _not_null(dev); 485 _not_null(map_name); 486 _not_set(ONLY_RESTORE); 487 _not_set(SAVE_TO_FILE); 488 _zero(hidden); 489 _zero(hidden_size_bytes); 490 _null(new_passphrase); 491 _null(new_prf_algo); 492 _zero(n_newkeyfiles); 493 _null(prf_algo); 494 _null(h_prf_algo); 495 _null(cipher_chain); 496 _null(h_cipher_chain); 497 498 if (!opts->protect_hidden) { 499 _zero(n_hkeyfiles); 500 //_null(h_passphrase); 501 } 502 503 return 0; 504 } 505 506 static 507 int 508 _opts_check_unmap(struct tcplay_opts *opts) 509 { 510 _not_null(map_name); 511 /* XXX: _not_null(dev); ? */ 512 _zero(nkeyfiles); 513 _zero(n_hkeyfiles); 514 _null(prf_algo); 515 _null(cipher_chain); 516 _null(h_prf_algo); 517 _null(h_cipher_chain); 518 _null(passphrase); 519 _null(h_passphrase); 520 _zero(hidden); 521 _zero(protect_hidden); 522 _null(new_prf_algo); 523 _null(new_passphrase); 524 _zero(n_newkeyfiles); 525 _not_set(SYS); 526 _not_set(FDE); 527 _not_set(BACKUP); 528 _not_set(ONLY_RESTORE); 529 _not_set(ALLOW_TRIM); 530 _not_set(SAVE_TO_FILE); 531 _not_set(HDR_FROM_FILE); 532 _not_set(H_HDR_FROM_FILE); 533 534 return 0; 535 } 536 537 static 538 int 539 _opts_check_info(struct tcplay_opts *opts) 540 { 541 _not_null(dev); 542 _null(map_name); 543 _not_set(ONLY_RESTORE); 544 _not_set(SAVE_TO_FILE); 545 _zero(hidden); 546 _zero(hidden_size_bytes); 547 _null(new_passphrase); 548 _null(new_prf_algo); 549 _zero(n_newkeyfiles); 550 _null(prf_algo); 551 _null(h_prf_algo); 552 _null(cipher_chain); 553 _null(h_cipher_chain); 554 555 if (!opts->protect_hidden) { 556 _zero(n_hkeyfiles); 557 //_null(h_passphrase); 558 } 559 560 return 0; 561 } 562 563 static 564 int 565 _opts_check_info_mapped(struct tcplay_opts *opts) 566 { 567 _not_null(map_name); 568 /* XXX: _not_null(dev); ? */ 569 _zero(nkeyfiles); 570 _zero(n_hkeyfiles); 571 _null(prf_algo); 572 _null(cipher_chain); 573 _null(h_prf_algo); 574 _null(h_cipher_chain); 575 _null(passphrase); 576 _null(h_passphrase); 577 _zero(hidden); 578 _zero(protect_hidden); 579 _null(new_prf_algo); 580 _null(new_passphrase); 581 _zero(n_newkeyfiles); 582 _not_set(SYS); 583 _not_set(FDE); 584 _not_set(BACKUP); 585 _not_set(ONLY_RESTORE); 586 _not_set(ALLOW_TRIM); 587 _not_set(SAVE_TO_FILE); 588 _not_set(HDR_FROM_FILE); 589 _not_set(H_HDR_FROM_FILE); 590 591 return 0; 592 } 593 594 static 595 int 596 _opts_check_modify(struct tcplay_opts *opts) 597 { 598 _not_null(dev); 599 _null(map_name); 600 _zero(hidden); 601 _zero(hidden_size_bytes); 602 _null(prf_algo); 603 _null(h_prf_algo); 604 _null(cipher_chain); 605 _null(h_cipher_chain); 606 607 if (!opts->protect_hidden) { 608 _zero(n_hkeyfiles); 609 _null(h_passphrase); 610 } 611 612 return 0; 613 } 614 615 616 static 617 int 618 _opts_check_restore(struct tcplay_opts *opts) 619 { 620 if ((_opts_check_modify(opts)) < 0) 621 return -1; 622 623 _null(new_prf_algo); 624 _zero(n_newkeyfiles); 625 _null(new_passphrase); 626 627 return 0; 628 } 629 630 int 631 tc_api_task_do(tc_api_task task) 632 { 633 struct tcplay_opts *opts; 634 int r = TC_OK; 635 636 if (task == NULL || ((opts = task->opts) == NULL)) { 637 errno = EFAULT; 638 return TC_ERR; 639 } 640 641 if (task->last_info != NULL) { 642 free_info(task->last_info); 643 } 644 645 switch (task->op) { 646 case TC_OP_CREATE: 647 if ((r = _opts_check_create(task->opts)) != 0) { 648 errno = EINVAL; 649 return r; 650 } 651 r = create_volume(opts); 652 break; 653 654 case TC_OP_MAP: 655 if ((r = _opts_check_map(task->opts)) != 0) { 656 errno = EINVAL; 657 return r; 658 } 659 r = map_volume(opts); 660 break; 661 662 case TC_OP_UNMAP: 663 if ((r = _opts_check_unmap(task->opts)) != 0) { 664 errno = EINVAL; 665 return r; 666 } 667 r = dm_teardown(opts->map_name, opts->dev); 668 break; 669 670 case TC_OP_INFO: 671 if ((r = _opts_check_info(task->opts)) != 0) { 672 errno = EINVAL; 673 return r; 674 } 675 if ((task->last_info = info_map_common(opts, NULL)) == NULL) { 676 r = TC_ERR; 677 } 678 break; 679 680 case TC_OP_INFO_MAPPED: 681 if ((r = _opts_check_info_mapped(task->opts)) != 0) { 682 errno = EINVAL; 683 return r; 684 } 685 if ((task->last_info = dm_info_map(opts->map_name)) == NULL) { 686 r = TC_ERR; 687 } 688 break; 689 690 case TC_OP_MODIFY: 691 if ((r = _opts_check_modify(task->opts)) != 0) { 692 errno = EINVAL; 693 return r; 694 } 695 r = modify_volume(opts); 696 break; 697 698 case TC_OP_RESTORE: 699 if ((r = _opts_check_restore(task->opts)) != 0) { 700 errno = EINVAL; 701 return r; 702 } 703 opts->flags |= TC_FLAG_ONLY_RESTORE; 704 r = modify_volume(opts); 705 opts->flags &= ~TC_FLAG_ONLY_RESTORE; 706 break; 707 } 708 709 return r; 710 } 711 712 713 int 714 tc_api_task_info_get(tc_api_task task, const char *key, ...) 715 { 716 char buf[1024]; 717 va_list ap; 718 struct tcplay_info *info; 719 char *s; 720 int *ip; 721 int64_t *i64p; 722 int r = TC_OK; 723 size_t sz; 724 725 if (task == NULL || ((info = task->last_info) == NULL)) { 726 errno = EFAULT; 727 return TC_ERR; 728 } 729 730 va_start(ap, key); 731 sz = va_arg(ap, size_t); 732 if (sz < 1) { 733 errno = EINVAL; 734 r = TC_ERR; 735 goto out; 736 } 737 738 if (_match(key, "device")) { 739 s = va_arg(ap, char *); 740 strncpy(s, info->dev, sz); 741 s[sz-1] = '\0'; 742 } else if (_match(key, "cipher")) { 743 s = va_arg(ap, char *); 744 tc_cipher_chain_sprint(buf, sizeof(buf), info->cipher_chain); 745 strncpy(s, buf, sz); 746 s[sz-1] = '\0'; 747 } else if (_match(key, "prf")) { 748 s = va_arg(ap, char *); 749 if (info->pbkdf_prf) 750 strncpy(s, info->pbkdf_prf->name, sz); 751 else 752 strncpy(s, "(unknown)", sz); 753 s[sz-1] = '\0'; 754 } else if (_match(key, "key_bits")) { 755 if (sz != sizeof(int)) { 756 errno = EFAULT; 757 r = TC_ERR; 758 goto out; 759 } 760 ip = va_arg(ap, int *); 761 *ip = 8*tc_cipher_chain_klen(info->cipher_chain); 762 } else if (_match(key, "size")) { 763 if (sz != sizeof(int64_t)) { 764 errno = EFAULT; 765 r = TC_ERR; 766 goto out; 767 } 768 i64p = va_arg(ap, int64_t *); 769 if (info->hdr) 770 *i64p = (int64_t)info->size * (int64_t)info->hdr->sec_sz; 771 else 772 *i64p = (int64_t)info->size * (int64_t)info->blk_sz; 773 } else if (_match(key, "iv_offset")) { 774 if (sz != sizeof(int64_t)) { 775 errno = EFAULT; 776 r = TC_ERR; 777 goto out; 778 } 779 i64p = va_arg(ap, int64_t *); 780 if (info->hdr) 781 *i64p = (int64_t)info->skip * (int64_t)info->hdr->sec_sz; 782 else 783 *i64p = (int64_t)info->skip * (int64_t)info->blk_sz; 784 } else if (_match(key, "block_offset")) { 785 if (sz != sizeof(int64_t)) { 786 errno = EFAULT; 787 r = TC_ERR; 788 goto out; 789 } 790 i64p = va_arg(ap, int64_t *); 791 if (info->hdr) 792 *i64p = (int64_t)info->offset * (int64_t)info->hdr->sec_sz; 793 else 794 *i64p = (int64_t)info->offset * (int64_t)info->blk_sz; 795 } else { 796 r = TC_ERR_UNIMPL; 797 } 798 799 out: 800 va_end(ap); 801 802 return r; 803 } 804