1 /* 2 * CDDL HEADER START 3 * 4 * The contents of this file are subject to the terms of the 5 * Common Development and Distribution License (the "License"). 6 * You may not use this file except in compliance with the License. 7 * 8 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE 9 * or http://www.opensolaris.org/os/licensing. 10 * See the License for the specific language governing permissions 11 * and limitations under the License. 12 * 13 * When distributing Covered Code, include this CDDL HEADER in each 14 * file and include the License file at usr/src/OPENSOLARIS.LICENSE. 15 * If applicable, add the following below this CDDL HEADER, with the 16 * fields enclosed by brackets "[]" replaced with your own identifying 17 * information: Portions Copyright [yyyy] [name of copyright owner] 18 * 19 * CDDL HEADER END 20 */ 21 /* 22 * Copyright 2009 Sun Microsystems, Inc. All rights reserved. 23 * Use is subject to license terms. 24 */ 25 26 #include <pthread.h> 27 #include <stdio.h> 28 #include <stdlib.h> 29 #include <string.h> 30 #include <security/cryptoki.h> 31 #include "softGlobal.h" 32 #include "softObject.h" 33 #include "softSession.h" 34 #include "softKeystore.h" 35 #include "softKeystoreUtil.h" 36 37 /* 38 * Add an object to the session's object list. 39 * 40 * This function will acquire the lock on the session, and release 41 * that lock after adding the object to the session's object list. 42 */ 43 void 44 soft_add_object_to_session(soft_object_t *objp, soft_session_t *sp) 45 { 46 47 /* Acquire the session lock. */ 48 (void) pthread_mutex_lock(&sp->session_mutex); 49 50 /* Insert the new object in front of session's object list. */ 51 if (sp->object_list == NULL) { 52 sp->object_list = objp; 53 objp->next = NULL; 54 objp->prev = NULL; 55 } else { 56 sp->object_list->prev = objp; 57 objp->next = sp->object_list; 58 objp->prev = NULL; 59 sp->object_list = objp; 60 } 61 62 /* Release the session lock. */ 63 (void) pthread_mutex_unlock(&sp->session_mutex); 64 } 65 66 67 /* 68 * Clean up and release the storage allocated to the object. 69 * 70 * The function is called either with the object lock being held 71 * (by caller soft_delete_object()), or there is no object lock 72 * yet (by soft_build_XXX_object() during creating an object). 73 */ 74 void 75 soft_cleanup_object(soft_object_t *objp) 76 { 77 /* 78 * Free the storage allocated to big integer attributes. 79 */ 80 soft_cleanup_object_bigint_attrs(objp); 81 82 /* 83 * Free the storage allocated to the extra attribute list. 84 */ 85 soft_cleanup_extra_attr(objp); 86 87 /* 88 * Free the storage allocated to certificate attributes. 89 */ 90 soft_cleanup_cert_object(objp); 91 } 92 93 94 /* 95 * Create a new object. Copy the attributes that can be modified 96 * (in the boolean attribute mask field and extra attribute list) 97 * from the old object to the new object. 98 * 99 * The caller of this function holds the lock on the old object. 100 */ 101 CK_RV 102 soft_copy_object(soft_object_t *old_object, soft_object_t **new_object, 103 CK_ULONG object_func, soft_session_t *sp) 104 { 105 106 CK_RV rv = CKR_OK; 107 soft_object_t *new_objp = NULL; 108 CK_ATTRIBUTE_INFO_PTR attrp; 109 110 /* Allocate new object. */ 111 new_objp = calloc(1, sizeof (soft_object_t)); 112 if (new_objp == NULL) 113 return (CKR_HOST_MEMORY); 114 115 new_objp->class = old_object->class; 116 new_objp->bool_attr_mask = old_object->bool_attr_mask; 117 new_objp->cert_type = old_object->cert_type; 118 new_objp->object_type = old_object->object_type; 119 120 attrp = old_object->extra_attrlistp; 121 while (attrp) { 122 /* 123 * Copy the attribute_info struct from the old 124 * object to a new attribute_info struct, and add 125 * that new struct to the extra attribute list 126 * of the new object. 127 */ 128 rv = soft_copy_extra_attr(attrp, new_objp); 129 if (rv != CKR_OK) { 130 soft_cleanup_extra_attr(new_objp); 131 free(new_objp); 132 return (rv); 133 } 134 attrp = attrp->next; 135 } 136 137 *new_object = new_objp; 138 139 if (object_func == SOFT_SET_ATTR_VALUE) { 140 /* done with copying all information that can be modified */ 141 return (CKR_OK); 142 } 143 144 /* 145 * Copy the rest of the object. 146 * Certain fields that are not appropriate for coping will be 147 * initialized. 148 */ 149 new_objp->key_type = old_object->key_type; 150 new_objp->magic_marker = old_object->magic_marker; 151 new_objp->mechanism = old_object->mechanism; 152 153 switch (object_func) { 154 case SOFT_COPY_OBJ_ORIG_SH: 155 new_objp->session_handle = old_object->session_handle; 156 break; 157 case SOFT_COPY_OBJECT: 158 /* 159 * Save the session handle of the C_CopyObject function 160 * in the new copy of the session object. 161 */ 162 new_objp->session_handle = (CK_SESSION_HANDLE)sp; 163 break; 164 } 165 166 (void) pthread_cond_init(&(new_objp->obj_free_cond), NULL); 167 (void) pthread_mutex_init(&(new_objp->object_mutex), NULL); 168 /* copy key related information */ 169 switch (new_objp->class) { 170 case CKO_PUBLIC_KEY: 171 rv = soft_copy_public_key_attr(OBJ_PUB(old_object), 172 &(OBJ_PUB(new_objp)), new_objp->key_type); 173 break; 174 case CKO_PRIVATE_KEY: 175 rv = soft_copy_private_key_attr(OBJ_PRI(old_object), 176 &(OBJ_PRI(new_objp)), new_objp->key_type); 177 break; 178 case CKO_SECRET_KEY: 179 rv = soft_copy_secret_key_attr(OBJ_SEC(old_object), 180 &(OBJ_SEC(new_objp))); 181 break; 182 case CKO_DOMAIN_PARAMETERS: 183 rv = soft_copy_domain_attr(OBJ_DOM(old_object), 184 &(OBJ_DOM(new_objp)), new_objp->key_type); 185 break; 186 case CKO_CERTIFICATE: 187 rv = soft_copy_certificate(OBJ_CERT(old_object), 188 &(OBJ_CERT(new_objp)), new_objp->cert_type); 189 break; 190 default: 191 /* should never be this case */ 192 break; 193 } 194 if (rv != CKR_OK) { 195 /* 196 * don't need to cleanup the memory from failure of copying 197 * any key related stuff. Each individual function for 198 * copying key attr will free the memory if it fails 199 */ 200 soft_cleanup_extra_attr(new_objp); 201 free(new_objp); 202 } 203 return (rv); 204 } 205 206 207 /* 208 * Copy the attributes (in the boolean attribute mask field and 209 * extra attribute list) from the new object back to the original 210 * object. Also, clean up and release all the storage in the extra 211 * attribute list of the original object. 212 * 213 * The caller of this function holds the lock on the old object. 214 */ 215 void 216 soft_merge_object(soft_object_t *old_object, soft_object_t *new_object) 217 { 218 old_object->bool_attr_mask = new_object->bool_attr_mask; 219 soft_cleanup_extra_attr(old_object); 220 old_object->extra_attrlistp = new_object->extra_attrlistp; 221 } 222 223 224 /* 225 * Create a new object struct, and add it to the session's object list. 226 */ 227 CK_RV 228 soft_add_object(CK_ATTRIBUTE_PTR pTemplate, CK_ULONG ulCount, 229 CK_ULONG *objecthandle_p, soft_session_t *sp) 230 { 231 232 CK_RV rv = CKR_OK; 233 soft_object_t *new_objp = NULL; 234 235 new_objp = calloc(1, sizeof (soft_object_t)); 236 if (new_objp == NULL) { 237 return (CKR_HOST_MEMORY); 238 } 239 240 new_objp->extra_attrlistp = NULL; 241 242 /* 243 * Validate attribute template and fill in the attributes 244 * in the soft_object_t. 245 */ 246 rv = soft_build_object(pTemplate, ulCount, new_objp); 247 if (rv != CKR_OK) { 248 goto fail_cleanup1; 249 } 250 251 rv = soft_pin_expired_check(new_objp); 252 if (rv != CKR_OK) { 253 goto fail_cleanup2; 254 } 255 256 rv = soft_object_write_access_check(sp, new_objp); 257 if (rv != CKR_OK) { 258 goto fail_cleanup2; 259 } 260 261 /* Initialize the rest of stuffs in soft_object_t. */ 262 (void) pthread_cond_init(&new_objp->obj_free_cond, NULL); 263 (void) pthread_mutex_init(&new_objp->object_mutex, NULL); 264 new_objp->magic_marker = SOFTTOKEN_OBJECT_MAGIC; 265 new_objp->obj_refcnt = 0; 266 new_objp->obj_delete_sync = 0; 267 268 /* Write the new token object to the keystore */ 269 if (IS_TOKEN_OBJECT(new_objp)) { 270 if (!soft_keystore_status(KEYSTORE_INITIALIZED)) { 271 rv = CKR_DEVICE_REMOVED; 272 goto fail_cleanup2; 273 } 274 new_objp->version = 1; 275 rv = soft_put_object_to_keystore(new_objp); 276 if (rv != CKR_OK) { 277 (void) pthread_cond_destroy(&new_objp->obj_free_cond); 278 (void) pthread_mutex_destroy(&new_objp->object_mutex); 279 goto fail_cleanup2; 280 } 281 new_objp->session_handle = (CK_SESSION_HANDLE)NULL; 282 soft_add_token_object_to_slot(new_objp); 283 /* 284 * Type casting the address of an object struct to 285 * an object handle. 286 */ 287 *objecthandle_p = (CK_ULONG)new_objp; 288 289 return (CKR_OK); 290 } 291 292 new_objp->session_handle = (CK_SESSION_HANDLE)sp; 293 294 /* Add the new object to the session's object list. */ 295 soft_add_object_to_session(new_objp, sp); 296 297 /* Type casting the address of an object struct to an object handle. */ 298 *objecthandle_p = (CK_ULONG)new_objp; 299 300 return (CKR_OK); 301 302 fail_cleanup2: 303 /* 304 * When any error occurs after soft_build_object(), we will need to 305 * clean up the memory allocated by the soft_build_object(). 306 */ 307 soft_cleanup_object(new_objp); 308 309 fail_cleanup1: 310 if (new_objp) { 311 /* 312 * The storage allocated inside of this object should have 313 * been cleaned up by the soft_build_object() if it failed. 314 * Therefore, we can safely free the object. 315 */ 316 free(new_objp); 317 } 318 319 return (rv); 320 321 } 322 323 324 /* 325 * Remove an object from the session's object list. 326 * 327 * The caller of this function holds the session lock. 328 */ 329 CK_RV 330 soft_remove_object_from_session(soft_object_t *objp, soft_session_t *sp) 331 { 332 soft_object_t *tmp_objp; 333 boolean_t found = B_FALSE; 334 335 /* 336 * Remove the object from the session's object list. 337 */ 338 if ((sp == NULL) || 339 (sp->magic_marker != SOFTTOKEN_SESSION_MAGIC)) { 340 return (CKR_SESSION_HANDLE_INVALID); 341 } 342 343 if ((sp->object_list == NULL) || (objp == NULL) || 344 (objp->magic_marker != SOFTTOKEN_OBJECT_MAGIC)) { 345 return (CKR_OBJECT_HANDLE_INVALID); 346 } 347 348 tmp_objp = sp->object_list; 349 while (tmp_objp) { 350 if (tmp_objp == objp) { 351 found = B_TRUE; 352 break; 353 } 354 tmp_objp = tmp_objp->next; 355 } 356 if (!found) 357 return (CKR_OBJECT_HANDLE_INVALID); 358 359 if (sp->object_list == objp) { 360 /* Object is the first one in the list. */ 361 if (objp->next) { 362 sp->object_list = objp->next; 363 objp->next->prev = NULL; 364 } else { 365 /* Object is the only one in the list. */ 366 sp->object_list = NULL; 367 } 368 } else { 369 /* Object is not the first one in the list. */ 370 if (objp->next) { 371 /* Object is in the middle of the list. */ 372 objp->prev->next = objp->next; 373 objp->next->prev = objp->prev; 374 } else { 375 /* Object is the last one in the list. */ 376 objp->prev->next = NULL; 377 } 378 } 379 return (CKR_OK); 380 } 381 382 /* 383 * This function adds the to-be-freed session object to a linked list. 384 * When the number of objects queued in the linked list reaches the 385 * maximum threshold MAX_OBJ_TO_BE_FREED, it will free the first 386 * object (FIFO) in the list. 387 */ 388 void 389 object_delay_free(soft_object_t *objp) 390 { 391 soft_object_t *tmp; 392 393 (void) pthread_mutex_lock(&obj_delay_freed.obj_to_be_free_mutex); 394 395 /* Add the newly deleted object at the end of the list */ 396 objp->next = NULL; 397 if (obj_delay_freed.first == NULL) { 398 obj_delay_freed.last = objp; 399 obj_delay_freed.first = objp; 400 } else { 401 obj_delay_freed.last->next = objp; 402 obj_delay_freed.last = objp; 403 } 404 405 if (++obj_delay_freed.count >= MAX_OBJ_TO_BE_FREED) { 406 /* 407 * Free the first object in the list only if 408 * the total count reaches maximum threshold. 409 */ 410 obj_delay_freed.count--; 411 tmp = obj_delay_freed.first->next; 412 free(obj_delay_freed.first); 413 obj_delay_freed.first = tmp; 414 } 415 (void) pthread_mutex_unlock(&obj_delay_freed.obj_to_be_free_mutex); 416 } 417 418 static void 419 soft_delete_object_cleanup(soft_object_t *objp, boolean_t force) 420 { 421 /* Acquire the lock on the object. */ 422 (void) pthread_mutex_lock(&objp->object_mutex); 423 424 /* 425 * Make sure another thread hasn't freed the object. 426 */ 427 if (objp->magic_marker != SOFTTOKEN_OBJECT_MAGIC) { 428 (void) pthread_mutex_unlock(&objp->object_mutex); 429 return; 430 } 431 432 /* 433 * The deletion of an object must be blocked when the object 434 * reference count is not zero. This means if any object related 435 * operation starts prior to the delete object operation gets in, 436 * the object deleting thread must wait for the non-deleting 437 * operation to be completed before it can proceed the delete 438 * operation. 439 * 440 * Unless we are being forced to shut everything down, this only 441 * happens if the libraries _fini() is running not of someone 442 * explicitly called C_Finalize(). 443 */ 444 if (force) 445 objp->obj_refcnt = 0; 446 447 while (objp->obj_refcnt != 0) { 448 /* 449 * We set the OBJECT_REFCNT_WAITING flag before we put 450 * this deleting thread in a wait state, so other non-deleting 451 * operation thread will signal to wake it up only when 452 * the object reference count becomes zero and this flag 453 * is set. 454 */ 455 objp->obj_delete_sync |= OBJECT_REFCNT_WAITING; 456 (void) pthread_cond_wait(&objp->obj_free_cond, 457 &objp->object_mutex); 458 } 459 460 objp->obj_delete_sync &= ~OBJECT_REFCNT_WAITING; 461 462 /* Mark object as no longer valid. */ 463 objp->magic_marker = 0; 464 465 (void) pthread_cond_destroy(&objp->obj_free_cond); 466 467 /* 468 * Cleanup the contents of this object such as free all the 469 * storage allocated for this object. 470 */ 471 soft_cleanup_object(objp); 472 473 /* Reset OBJECT_IS_DELETING flag. */ 474 objp->obj_delete_sync &= ~OBJECT_IS_DELETING; 475 476 (void) pthread_mutex_unlock(&objp->object_mutex); 477 /* Destroy the object lock */ 478 (void) pthread_mutex_destroy(&objp->object_mutex); 479 480 /* Free the object itself */ 481 if (IS_TOKEN_OBJECT(objp)) 482 free(objp); 483 else 484 /* 485 * Delay freeing the session object as S1WS/NSS uses session 486 * objects for its SSL Handshake. 487 */ 488 (void) object_delay_free(objp); 489 } 490 491 /* 492 * Delete an object: 493 * - Remove the object from the session's object list. 494 * Holding the lock on the session which the object was created at 495 * is needed to do this. 496 * - Release the storage allocated to the object. 497 * 498 * The boolean argument lock_held is used to indicate that whether 499 * the caller holds the session lock or not. 500 * - When called by soft_delete_all_objects_in_session() -- the 501 * lock_held = TRUE. 502 * 503 * When the caller does not hold the session lock, this function 504 * will acquire that lock in order to proceed, and also release 505 * that lock before returning to caller. 506 */ 507 void 508 soft_delete_object(soft_session_t *sp, soft_object_t *objp, 509 boolean_t force, boolean_t lock_held) 510 { 511 512 /* 513 * Check to see if the caller holds the lock on the session. 514 * If not, we need to acquire that lock in order to proceed. 515 */ 516 if (!lock_held) { 517 /* Acquire the session lock. */ 518 (void) pthread_mutex_lock(&sp->session_mutex); 519 } 520 521 /* Remove the object from the session's object list first. */ 522 if (soft_remove_object_from_session(objp, sp) != CKR_OK) { 523 if (!lock_held) { 524 (void) pthread_mutex_unlock(&sp->session_mutex); 525 } 526 return; 527 } 528 529 if (!lock_held) { 530 /* 531 * If the session lock is obtained by this function, 532 * then release that lock after removing the object 533 * from session's object list. 534 * We want the releasing of the object storage to 535 * be done without holding the session lock. 536 */ 537 (void) pthread_mutex_unlock(&sp->session_mutex); 538 } 539 540 soft_delete_object_cleanup(objp, force); 541 } 542 543 544 /* 545 * Delete all the objects in a session. The caller holds the lock 546 * on the session. 547 */ 548 void 549 soft_delete_all_objects_in_session(soft_session_t *sp, boolean_t force) 550 { 551 soft_object_t *objp = sp->object_list; 552 soft_object_t *objp1; 553 554 /* Delete all the objects in the session. */ 555 while (objp) { 556 objp1 = objp->next; 557 558 /* 559 * Delete an object by calling soft_delete_object() 560 * with a TRUE boolean argument indicating that 561 * the caller holds the lock on the session. 562 */ 563 soft_delete_object(sp, objp, force, B_TRUE); 564 565 objp = objp1; 566 } 567 } 568 569 static CK_RV 570 add_to_search_result(soft_object_t *obj, find_context_t *fcontext, 571 CK_ULONG *num_result_alloc) 572 { 573 /* 574 * allocate space for storing results if the currently 575 * allocated space is not enough 576 */ 577 if (*num_result_alloc <= fcontext->num_results) { 578 fcontext->objs_found = realloc(fcontext->objs_found, 579 sizeof (soft_object_t *) * (*num_result_alloc + BUFSIZ)); 580 if (fcontext->objs_found == NULL) { 581 return (CKR_HOST_MEMORY); 582 } 583 *num_result_alloc += BUFSIZ; 584 } 585 586 (fcontext->objs_found)[(fcontext->num_results)++] = obj; 587 return (CKR_OK); 588 } 589 590 static CK_RV 591 search_for_objects(CK_ATTRIBUTE_PTR pTemplate, CK_ULONG ulCount, 592 find_context_t *fcontext) 593 { 594 soft_session_t *session_p; 595 soft_object_t *obj; 596 CK_OBJECT_CLASS pclasses[6]; /* classes attrs possibly exist */ 597 CK_ULONG num_pclasses; /* number of possible classes */ 598 CK_ULONG num_result_alloc = 0; /* spaces allocated for results */ 599 CK_RV rv = CKR_OK; 600 /* whether CKA_TOKEN flag specified or not */ 601 boolean_t token_specified = B_FALSE; 602 /* value of CKA_TOKEN flag, if specified */ 603 boolean_t token_flag_val = B_FALSE; 604 CK_ULONG i; 605 606 if (ulCount > 0) { 607 /* there are some search requirement */ 608 soft_process_find_attr(pclasses, &num_pclasses, 609 pTemplate, ulCount); 610 } 611 612 for (i = 0; i < ulCount; i++) { 613 if (pTemplate[i].type == CKA_PRIVATE) { 614 (void) pthread_mutex_lock(&soft_giant_mutex); 615 if (soft_slot.userpin_change_needed) { 616 (void) pthread_mutex_unlock(&soft_giant_mutex); 617 return (CKR_PIN_EXPIRED); 618 } 619 (void) pthread_mutex_unlock(&soft_giant_mutex); 620 } 621 } 622 623 /* 624 * look through template and see if it explicitly specifies 625 * whether we need to look for token objects or not 626 */ 627 for (i = 0; i < ulCount; i++) { 628 if (pTemplate[i].type == CKA_TOKEN) { 629 token_specified = B_TRUE; 630 token_flag_val = *((CK_BBOOL *)pTemplate[i].pValue); 631 break; 632 } 633 } 634 635 /* 636 * Need go through token objects if it explicitly say so, or 637 * it is not mentioned in the template. And this will ONLY be 638 * done when the keystore exists. Otherwise, we will skip re-loading 639 * the token objects. 640 * 641 * If a session has not logged into the token, only public 642 * objects, if any, will be searched. If a session is logged 643 * into the token, all public and private objects in the keystore 644 * are searched. 645 */ 646 if (((token_flag_val) || (!token_specified)) && 647 soft_keystore_status(KEYSTORE_INITIALIZED)) { 648 /* acquire token session lock */ 649 (void) pthread_mutex_lock(&soft_slot.slot_mutex); 650 rv = refresh_token_objects(); 651 if (rv != CKR_OK) { 652 (void) pthread_mutex_unlock(&soft_slot.slot_mutex); 653 return (rv); 654 } 655 obj = soft_slot.token_object_list; 656 while (obj) { 657 (void) pthread_mutex_lock(&obj->object_mutex); 658 if (((token_specified) && (ulCount > 1)) || 659 ((!token_specified) && (ulCount > 0))) { 660 if (soft_find_match_attrs(obj, pclasses, 661 num_pclasses, pTemplate, ulCount)) { 662 rv = add_to_search_result( 663 obj, fcontext, &num_result_alloc); 664 } 665 } else { 666 /* no search criteria, just record the object */ 667 rv = add_to_search_result(obj, fcontext, 668 &num_result_alloc); 669 } 670 (void) pthread_mutex_unlock(&obj->object_mutex); 671 if (rv != CKR_OK) { 672 (void) pthread_mutex_unlock 673 (&soft_slot.slot_mutex); 674 return (rv); 675 } 676 obj = obj->next; 677 } 678 (void) pthread_mutex_unlock(&soft_slot.slot_mutex); 679 } 680 681 if (token_flag_val) { 682 /* no need to look through session objects */ 683 return (rv); 684 } 685 686 /* Acquire the global session list lock */ 687 (void) pthread_mutex_lock(&soft_sessionlist_mutex); 688 689 /* 690 * Go through all objects in each session. 691 * Acquire individual session lock for the session 692 * we are searching. 693 */ 694 session_p = soft_session_list; 695 while (session_p) { 696 (void) pthread_mutex_lock(&session_p->session_mutex); 697 698 obj = session_p->object_list; 699 while (obj) { 700 (void) pthread_mutex_lock(&obj->object_mutex); 701 if (ulCount > 0) { 702 if (soft_find_match_attrs(obj, pclasses, 703 num_pclasses, pTemplate, ulCount)) { 704 rv = add_to_search_result( 705 obj, fcontext, &num_result_alloc); 706 } 707 } else { 708 /* no search criteria, just record the object */ 709 rv = add_to_search_result(obj, fcontext, 710 &num_result_alloc); 711 } 712 (void) pthread_mutex_unlock(&obj->object_mutex); 713 if (rv != CKR_OK) { 714 (void) pthread_mutex_unlock( 715 &session_p->session_mutex); 716 goto cleanup; 717 } 718 obj = obj->next; 719 } 720 (void) pthread_mutex_unlock(&session_p->session_mutex); 721 session_p = session_p->next; 722 } 723 724 cleanup: 725 /* Release the global session list lock */ 726 (void) pthread_mutex_unlock(&soft_sessionlist_mutex); 727 return (rv); 728 } 729 730 /* 731 * Initialize the context for C_FindObjects() calls 732 */ 733 CK_RV 734 soft_find_objects_init(soft_session_t *sp, CK_ATTRIBUTE_PTR pTemplate, 735 CK_ULONG ulCount) 736 { 737 738 CK_RV rv = CKR_OK; 739 CK_OBJECT_CLASS class; /* for soft_validate_attr(). Value unused */ 740 find_context_t *fcontext; 741 742 if (ulCount) { 743 rv = soft_validate_attr(pTemplate, ulCount, &class); 744 /* Make sure all attributes in template are valid */ 745 if (rv != CKR_OK) { 746 return (rv); 747 } 748 } 749 750 751 /* prepare the find context */ 752 fcontext = calloc(1, sizeof (find_context_t)); 753 if (fcontext == NULL) { 754 return (CKR_HOST_MEMORY); 755 } 756 757 rv = search_for_objects(pTemplate, ulCount, fcontext); 758 if (rv != CKR_OK) { 759 free(fcontext); 760 return (rv); 761 } 762 763 /* store the find_context in the session */ 764 sp->find_objects.context = (CK_VOID_PTR)fcontext; 765 766 return (rv); 767 } 768 769 void 770 soft_find_objects_final(soft_session_t *sp) 771 { 772 find_context_t *fcontext; 773 774 fcontext = sp->find_objects.context; 775 sp->find_objects.context = NULL; 776 sp->find_objects.flags = 0; 777 if (fcontext->objs_found != NULL) { 778 free(fcontext->objs_found); 779 } 780 781 free(fcontext); 782 } 783 784 void 785 soft_find_objects(soft_session_t *sp, CK_OBJECT_HANDLE *obj_found, 786 CK_ULONG max_obj_requested, CK_ULONG *found_obj_count) 787 { 788 find_context_t *fcontext; 789 CK_ULONG num_obj_found = 0; 790 CK_ULONG i; 791 soft_object_t *obj; 792 793 fcontext = sp->find_objects.context; 794 795 for (i = fcontext->next_result_index; 796 ((num_obj_found < max_obj_requested) && 797 (i < fcontext->num_results)); 798 i++) { 799 obj = fcontext->objs_found[i]; 800 if (obj != NULL) { 801 (void) pthread_mutex_lock(&obj->object_mutex); 802 /* a sanity check to make sure the obj is still valid */ 803 if (obj->magic_marker == SOFTTOKEN_OBJECT_MAGIC) { 804 obj_found[num_obj_found] = 805 (CK_OBJECT_HANDLE)obj; 806 num_obj_found++; 807 } 808 (void) pthread_mutex_unlock(&obj->object_mutex); 809 } 810 } 811 fcontext->next_result_index = i; 812 *found_obj_count = num_obj_found; 813 } 814 815 /* 816 * Below are the token object related functions 817 */ 818 void 819 soft_add_token_object_to_slot(soft_object_t *objp) 820 { 821 822 (void) pthread_mutex_lock(&soft_slot.slot_mutex); 823 824 /* Insert the new object in front of slot's token object list. */ 825 if (soft_slot.token_object_list == NULL) { 826 soft_slot.token_object_list = objp; 827 objp->next = NULL; 828 objp->prev = NULL; 829 } else { 830 soft_slot.token_object_list->prev = objp; 831 objp->next = soft_slot.token_object_list; 832 objp->prev = NULL; 833 soft_slot.token_object_list = objp; 834 } 835 836 (void) pthread_mutex_unlock(&soft_slot.slot_mutex); 837 838 } 839 840 void 841 soft_remove_token_object_from_slot(soft_object_t *objp, boolean_t lock_held) 842 { 843 844 if (!lock_held) 845 (void) pthread_mutex_lock(&soft_slot.slot_mutex); 846 847 /* 848 * Remove the object from the slot's token object list. 849 */ 850 if (soft_slot.token_object_list == objp) { 851 /* Object is the first one in the list. */ 852 if (objp->next) { 853 soft_slot.token_object_list = objp->next; 854 objp->next->prev = NULL; 855 } else { 856 /* Object is the only one in the list. */ 857 soft_slot.token_object_list = NULL; 858 } 859 } else { 860 /* Object is not the first one in the list. */ 861 if (objp->next) { 862 /* Object is in the middle of the list. */ 863 objp->prev->next = objp->next; 864 objp->next->prev = objp->prev; 865 } else { 866 /* Object is the last one in the list. */ 867 objp->prev->next = NULL; 868 } 869 } 870 871 if (!lock_held) 872 (void) pthread_mutex_unlock(&soft_slot.slot_mutex); 873 } 874 875 void 876 soft_delete_token_object(soft_object_t *objp, boolean_t persistent, 877 boolean_t lock_held) 878 { 879 880 if (!lock_held) 881 (void) pthread_mutex_lock(&soft_slot.slot_mutex); 882 if (persistent) 883 /* Delete the object from the keystore. */ 884 (void) soft_keystore_del_obj(&objp->ks_handle, B_FALSE); 885 886 /* Remove the object from the slot's token object list. */ 887 soft_remove_token_object_from_slot(objp, B_TRUE); 888 if (!lock_held) 889 (void) pthread_mutex_unlock(&soft_slot.slot_mutex); 890 891 soft_delete_object_cleanup(objp, B_FALSE); 892 } 893 894 void 895 soft_delete_all_in_core_token_objects(token_obj_type_t type) 896 { 897 898 soft_object_t *objp; 899 soft_object_t *objp1; 900 901 (void) pthread_mutex_lock(&soft_slot.slot_mutex); 902 objp = soft_slot.token_object_list; 903 904 switch (type) { 905 case PRIVATE_TOKEN: 906 while (objp) { 907 objp1 = objp->next; 908 if (objp->object_type == TOKEN_PRIVATE) { 909 soft_delete_token_object(objp, B_FALSE, B_TRUE); 910 } 911 objp = objp1; 912 } 913 break; 914 915 case PUBLIC_TOKEN: 916 while (objp) { 917 objp1 = objp->next; 918 if (objp->object_type == TOKEN_PUBLIC) { 919 soft_delete_token_object(objp, B_FALSE, B_TRUE); 920 } 921 objp = objp1; 922 } 923 break; 924 925 case ALL_TOKEN: 926 while (objp) { 927 objp1 = objp->next; 928 soft_delete_token_object(objp, B_FALSE, B_TRUE); 929 objp = objp1; 930 } 931 break; 932 } 933 934 (void) pthread_mutex_unlock(&soft_slot.slot_mutex); 935 936 } 937 938 /* 939 * Mark all the token objects in the global list to be valid. 940 */ 941 void 942 soft_validate_token_objects(boolean_t validate) 943 { 944 945 soft_object_t *objp; 946 947 (void) pthread_mutex_lock(&soft_slot.slot_mutex); 948 949 objp = soft_slot.token_object_list; 950 951 while (objp) { 952 if (validate) 953 objp->magic_marker = SOFTTOKEN_OBJECT_MAGIC; 954 else 955 objp->magic_marker = 0; 956 957 objp = objp->next; 958 } 959 960 (void) pthread_mutex_unlock(&soft_slot.slot_mutex); 961 962 } 963 964 /* 965 * Verify user's write access rule to the token object. 966 */ 967 CK_RV 968 soft_object_write_access_check(soft_session_t *sp, soft_object_t *objp) 969 { 970 971 /* 972 * This function is called by C_CreateObject, C_CopyObject, 973 * C_DestroyObject, C_SetAttributeValue, C_GenerateKey, 974 * C_GenerateKeyPairs, C_DeriveKey. All of them will write 975 * the token object to the keystore. 976 */ 977 (void) pthread_mutex_lock(&soft_giant_mutex); 978 if (!soft_slot.authenticated) { 979 (void) pthread_mutex_unlock(&soft_giant_mutex); 980 /* User is not logged in */ 981 if (sp->flags & CKF_RW_SESSION) { 982 /* 983 * For R/W Public Session: 984 * we allow write access to public session or token 985 * object, but not for private token/session object. 986 */ 987 if ((objp->object_type == TOKEN_PRIVATE) || 988 (objp->object_type == SESSION_PRIVATE)) { 989 return (CKR_USER_NOT_LOGGED_IN); 990 } 991 } else { 992 /* 993 * For R/O Public Session: 994 * we allow write access to public session object. 995 */ 996 if (objp->object_type != SESSION_PUBLIC) 997 return (CKR_SESSION_READ_ONLY); 998 } 999 } else { 1000 (void) pthread_mutex_unlock(&soft_giant_mutex); 1001 /* User is logged in */ 1002 if (!(sp->flags & CKF_RW_SESSION)) { 1003 /* 1004 * For R/O User Function Session: 1005 * we allow write access to public or private 1006 * session object, but not for public or private 1007 * token object. 1008 */ 1009 if ((objp->object_type == TOKEN_PUBLIC) || 1010 (objp->object_type == TOKEN_PRIVATE)) { 1011 return (CKR_SESSION_READ_ONLY); 1012 } 1013 } 1014 } 1015 1016 return (CKR_OK); 1017 } 1018 1019 /* 1020 * Verify if user is required to setpin when accessing the 1021 * private token/session object. 1022 */ 1023 CK_RV 1024 soft_pin_expired_check(soft_object_t *objp) 1025 { 1026 1027 /* 1028 * This function is called by C_CreateObject, C_CopyObject, 1029 * C_DestroyObject, C_GenerateKey, 1030 * C_GenerateKeyPairs, C_DeriveKey. 1031 * All of them will return CKR_PIN_EXPIRED if the 1032 * "userpin_change_needed" is set. 1033 * 1034 * The following functions will not be necessary to call 1035 * this routine even though CKR_PIN_EXPIRED is one of the 1036 * valid error code they might return. These functions are: 1037 * C_EncryptInit, C_DecryptInit, C_DigestInit, C_SignInit, 1038 * C_SignRecoverInit, C_VerifyInit, C_VerifyRecoverInit. 1039 * This is because they will not get the object handle 1040 * before the above functions are called. 1041 */ 1042 1043 (void) pthread_mutex_lock(&soft_giant_mutex); 1044 if (soft_slot.userpin_change_needed) { 1045 /* 1046 * Access private token/session object but user's 1047 * PIN is expired or never set. 1048 */ 1049 if ((objp->object_type == TOKEN_PRIVATE) || 1050 (objp->object_type == SESSION_PRIVATE)) { 1051 (void) pthread_mutex_unlock(&soft_giant_mutex); 1052 return (CKR_PIN_EXPIRED); 1053 } 1054 } 1055 1056 (void) pthread_mutex_unlock(&soft_giant_mutex); 1057 return (CKR_OK); 1058 } 1059 1060 /* 1061 * Copy the selected fields from new token object to old 1062 * token object. 1063 */ 1064 CK_RV 1065 soft_copy_to_old_object(soft_object_t *new, soft_object_t *old) 1066 { 1067 1068 CK_RV rv = CKR_OK; 1069 CK_ATTRIBUTE_INFO_PTR attrp; 1070 1071 old->class = new->class; 1072 old->bool_attr_mask = new->bool_attr_mask; 1073 soft_cleanup_extra_attr(old); 1074 attrp = new->extra_attrlistp; 1075 while (attrp) { 1076 rv = soft_copy_extra_attr(attrp, old); 1077 if (rv != CKR_OK) { 1078 soft_cleanup_extra_attr(old); 1079 return (rv); 1080 } 1081 attrp = attrp->next; 1082 } 1083 1084 /* Done with copying all information that can be modified */ 1085 return (CKR_OK); 1086 } 1087 1088 /* 1089 * Update an existing object with new data from keystore. 1090 */ 1091 CK_RV 1092 soft_update_object(ks_obj_t *ks_obj, soft_object_t *old_obj) 1093 { 1094 1095 soft_object_t *new_object; 1096 CK_RV rv; 1097 1098 new_object = calloc(1, sizeof (soft_object_t)); 1099 if (new_object == NULL) 1100 return (CKR_HOST_MEMORY); 1101 1102 rv = soft_keystore_unpack_obj(new_object, ks_obj); 1103 if (rv != CKR_OK) { 1104 soft_cleanup_object(new_object); 1105 free(new_object); 1106 return (rv); 1107 } 1108 rv = soft_copy_to_old_object(new_object, old_obj); 1109 1110 soft_cleanup_object(new_object); 1111 free(new_object); 1112 return (CKR_OK); 1113 } 1114 1115 1116 CK_RV 1117 soft_keystore_load_latest_object(soft_object_t *old_obj) 1118 { 1119 1120 uint_t version; 1121 ks_obj_t *ks_obj = NULL; 1122 CK_RV rv = CKR_OK; 1123 1124 /* 1125 * Get the current version number from the keystore for 1126 * the specified token object. 1127 */ 1128 if (soft_keystore_get_object_version(&old_obj->ks_handle, &version, 1129 B_FALSE) == 1) 1130 return (CKR_FUNCTION_FAILED); 1131 1132 /* 1133 * If the keystore version is newer than the in-core version, 1134 * re-read the token object from the keystore. 1135 */ 1136 if (old_obj->version != version) { 1137 rv = soft_keystore_get_single_obj(&old_obj->ks_handle, 1138 &ks_obj, B_FALSE); 1139 if (rv != CKR_OK) 1140 return (rv); 1141 old_obj->version = version; 1142 1143 /* 1144 * Update an existing object with new data from keystore. 1145 */ 1146 rv = soft_update_object(ks_obj, old_obj); 1147 free(ks_obj->buf); 1148 free(ks_obj); 1149 } 1150 1151 return (rv); 1152 } 1153 1154 /* 1155 * Insert an object into a list of soft_object_t objects. It is assumed 1156 * that the object to be inserted doesn't previously belong to any list 1157 */ 1158 static void 1159 insert_into_list(soft_object_t **list, soft_object_t **end_of_list, 1160 soft_object_t *objp) 1161 { 1162 if (*list == NULL) { 1163 *list = objp; 1164 objp->next = NULL; 1165 objp->prev = NULL; 1166 *end_of_list = objp; 1167 } else { 1168 (*list)->prev = objp; 1169 objp->next = *list; 1170 objp->prev = NULL; 1171 *list = objp; 1172 } 1173 } 1174 1175 /* 1176 * Move an object from an existing list into a new list of 1177 * soft_object_t objects. 1178 */ 1179 static void 1180 move_into_list(soft_object_t **existing_list, soft_object_t **new_list, 1181 soft_object_t **end_of_list, soft_object_t *objp) 1182 { 1183 1184 /* first, remove object from existing list */ 1185 if (objp == *existing_list) { 1186 /* first item in list */ 1187 if (objp->next) { 1188 *existing_list = objp->next; 1189 objp->next->prev = NULL; 1190 } else { 1191 *existing_list = NULL; 1192 } 1193 } else { 1194 if (objp->next) { 1195 objp->prev->next = objp->next; 1196 objp->next->prev = objp->prev; 1197 } else { 1198 objp->prev->next = NULL; 1199 } 1200 } 1201 1202 /* then, add into new list */ 1203 insert_into_list(new_list, end_of_list, objp); 1204 } 1205 1206 /* 1207 * Insert "new_list" into "existing_list", new list will always be inserted 1208 * into the front of existing list 1209 */ 1210 static void 1211 insert_list_into_list(soft_object_t **existing_list, 1212 soft_object_t *new_list, soft_object_t *end_new_list) 1213 { 1214 1215 if (new_list == NULL) { 1216 return; 1217 } 1218 1219 if (*existing_list == NULL) { 1220 *existing_list = new_list; 1221 } else { 1222 (*existing_list)->prev = end_new_list; 1223 end_new_list->next = *existing_list; 1224 *existing_list = new_list; 1225 } 1226 } 1227 1228 static void 1229 delete_all_objs_in_list(soft_object_t *list) 1230 { 1231 soft_object_t *objp, *objp_next; 1232 1233 if (list == NULL) { 1234 return; 1235 } 1236 1237 objp = list; 1238 while (objp) { 1239 objp_next = objp->next; 1240 soft_delete_object_cleanup(objp, B_FALSE); 1241 objp = objp_next; 1242 } 1243 } 1244 1245 /* 1246 * Makes sure that the list of in-core token objects are up to date 1247 * with respect to the on disk keystore. Other process/applications 1248 * might have modified the keystore since the objects are last loaded 1249 * 1250 * If there's any error from refreshing the token object list (eg: unable 1251 * to read, unable to unpack and object...etc), the in-core list 1252 * will be restored back to the state before the refresh. An error 1253 * will be returned to indicate the failure. 1254 * 1255 * It is assumed that the caller holds the lock for the token slot 1256 */ 1257 CK_RV 1258 refresh_token_objects() 1259 { 1260 uint_t on_disk_ks_version; 1261 ks_obj_t *on_disk_list = NULL, *tmp_on_disk, *next_on_disk; 1262 soft_object_t *in_core_obj, *tmp_incore_obj, *new_objp = NULL; 1263 CK_RV rv = CKR_OK; 1264 1265 /* deleted in-core objects */ 1266 soft_object_t *del_objs_list = NULL; 1267 soft_object_t *end_del_objs_list = NULL; 1268 1269 /* modified in-core objects */ 1270 soft_object_t *mod_objs_list = NULL; 1271 soft_object_t *end_mod_objs_list = NULL; 1272 1273 /* 1274 * copy of modified in-core objects, in case we need 1275 * undo the change 1276 */ 1277 soft_object_t *copy_of_mod_objs_list = NULL; 1278 soft_object_t *end_copy_of_mod_objs_list = NULL; 1279 1280 /* objects to be added to the in-core list */ 1281 soft_object_t *added_objs_list = NULL; 1282 soft_object_t *end_added_objs_list = NULL; 1283 1284 if (soft_keystore_get_version(&on_disk_ks_version, B_FALSE) != 0) { 1285 return (CKR_FUNCTION_FAILED); 1286 } 1287 1288 (void) pthread_mutex_lock(&soft_giant_mutex); 1289 if (on_disk_ks_version == soft_slot.ks_version) { 1290 /* no change */ 1291 (void) pthread_mutex_unlock(&soft_giant_mutex); 1292 return (CKR_OK); 1293 } 1294 1295 if (soft_slot.authenticated) { 1296 /* get both public and private objects */ 1297 (void) pthread_mutex_unlock(&soft_giant_mutex); 1298 rv = soft_keystore_get_objs(ALL_TOKENOBJS, &on_disk_list, 1299 B_FALSE); 1300 } else { 1301 /* get both public objects only */ 1302 (void) pthread_mutex_unlock(&soft_giant_mutex); 1303 rv = soft_keystore_get_objs(PUB_TOKENOBJS, &on_disk_list, 1304 B_FALSE); 1305 } 1306 if (rv != CKR_OK) { 1307 return (rv); 1308 } 1309 1310 /* 1311 * The in-core tokens list will be updated as follows: 1312 * 1313 * Go through each item in the in-core tokens list. 1314 * Try to match the in-core object with one of the 1315 * objects from the on-disk list. If a match is made, 1316 * check the version number, and update in-core object 1317 * as necessary. 1318 * 1319 * If there's no match between in-core object with on-disk 1320 * object, that means the object is deleted since 1321 * last loaded. Will remove object from in-core list. 1322 * 1323 * When doing the matching of on-disk object list above, 1324 * Delete every matched on-disk object from the on-disk list 1325 * regardless the in-core object need to be deleted or not 1326 * 1327 * At the end of matching the in-core tokens list, if 1328 * any object is still left on the on-disk object list, 1329 * those are all new objects added since last load, 1330 * include all of them to the in-core list 1331 * 1332 * Since we need to be able to revert the in-core list 1333 * back to original state if there's any error with the refresh, 1334 * we need to do the following. 1335 * When an in-core object is "deleted", it is not immediately 1336 * deleted. It is moved to the list of "deleted_objects". 1337 * When an in-core object is "modified", a copy of the 1338 * unmodified object is made. After the object is modified, 1339 * it is temporarily moved to the "mod_objects" list 1340 * from the in-core list. 1341 * When the refresh is completed without any error, 1342 * the actual deleted objects and unmodified objects is deleted. 1343 */ 1344 in_core_obj = soft_slot.token_object_list; 1345 while (in_core_obj) { 1346 /* try to match object with on_disk_list */ 1347 ks_obj_t *ondisk_obj, *prev_ondisk_obj; 1348 boolean_t found = B_FALSE; 1349 soft_object_t *obj_copy; 1350 1351 ondisk_obj = on_disk_list; 1352 prev_ondisk_obj = NULL; 1353 1354 /* larval object that has not been written to disk */ 1355 if (in_core_obj->ks_handle.name[0] == '\0') { 1356 in_core_obj = in_core_obj->next; 1357 continue; 1358 } 1359 1360 while ((!found) && (ondisk_obj != NULL)) { 1361 1362 if (strcmp((char *)((ondisk_obj->ks_handle).name), 1363 (char *)((in_core_obj->ks_handle).name)) == 0) { 1364 1365 /* found a match */ 1366 found = B_TRUE; 1367 1368 /* update in-core obj if necessary */ 1369 if (ondisk_obj->obj_version != 1370 in_core_obj->version) { 1371 /* make a copy of before updating */ 1372 rv = soft_copy_object(in_core_obj, 1373 &obj_copy, SOFT_COPY_OBJ_ORIG_SH, 1374 NULL); 1375 if (rv != CKR_OK) { 1376 goto cleanup; 1377 } 1378 insert_into_list( 1379 ©_of_mod_objs_list, 1380 &end_copy_of_mod_objs_list, 1381 obj_copy); 1382 1383 rv = soft_update_object(ondisk_obj, 1384 in_core_obj); 1385 if (rv != CKR_OK) { 1386 goto cleanup; 1387 } 1388 move_into_list( 1389 &(soft_slot.token_object_list), 1390 &mod_objs_list, &end_mod_objs_list, 1391 in_core_obj); 1392 } 1393 1394 /* remove processed obj from on disk list */ 1395 if (ondisk_obj == on_disk_list) { 1396 /* first item */ 1397 on_disk_list = ondisk_obj->next; 1398 } else { 1399 prev_ondisk_obj->next = 1400 ondisk_obj->next; 1401 } 1402 free(ondisk_obj->buf); 1403 free(ondisk_obj); 1404 } else { 1405 prev_ondisk_obj = ondisk_obj; 1406 ondisk_obj = ondisk_obj->next; 1407 } 1408 } 1409 1410 if (!found) { 1411 tmp_incore_obj = in_core_obj->next; 1412 move_into_list(&(soft_slot.token_object_list), 1413 &del_objs_list, &end_del_objs_list, in_core_obj); 1414 in_core_obj = tmp_incore_obj; 1415 } else { 1416 in_core_obj = in_core_obj->next; 1417 } 1418 } 1419 1420 /* 1421 * At this point, if there's still anything on the on_disk_list, they 1422 * are all newly added objects since in-core list last loaded. 1423 * include all of them into the in-core list 1424 */ 1425 next_on_disk = on_disk_list; 1426 while (next_on_disk) { 1427 new_objp = calloc(1, sizeof (soft_object_t)); 1428 if (new_objp == NULL) { 1429 rv = CKR_HOST_MEMORY; 1430 goto cleanup; 1431 } 1432 1433 /* Convert the keystore format to memory format */ 1434 rv = soft_keystore_unpack_obj(new_objp, next_on_disk); 1435 if (rv != CKR_OK) { 1436 soft_cleanup_object(new_objp); 1437 free(new_objp); 1438 goto cleanup; 1439 } 1440 1441 insert_into_list(&added_objs_list, &end_added_objs_list, 1442 new_objp); 1443 1444 /* free the on_disk object */ 1445 tmp_on_disk = next_on_disk; 1446 next_on_disk = tmp_on_disk->next; 1447 free(tmp_on_disk->buf); 1448 free(tmp_on_disk); 1449 } 1450 1451 if (rv == CKR_OK) { 1452 (void) pthread_mutex_lock(&soft_giant_mutex); 1453 soft_slot.ks_version = on_disk_ks_version; 1454 (void) pthread_mutex_unlock(&soft_giant_mutex); 1455 1456 /* add the new objects into in-core list */ 1457 insert_list_into_list(&(soft_slot.token_object_list), 1458 added_objs_list, end_added_objs_list); 1459 1460 /* add modified objects back into the in-core list */ 1461 insert_list_into_list(&(soft_slot.token_object_list), 1462 mod_objs_list, end_mod_objs_list); 1463 1464 /* actually remove deleted objs, and copy of modified objs */ 1465 delete_all_objs_in_list(copy_of_mod_objs_list); 1466 delete_all_objs_in_list(del_objs_list); 1467 } 1468 1469 return (rv); 1470 1471 cleanup: 1472 next_on_disk = on_disk_list; 1473 while (next_on_disk) { 1474 tmp_on_disk = next_on_disk; 1475 next_on_disk = tmp_on_disk->next; 1476 free(tmp_on_disk->buf); 1477 free(tmp_on_disk); 1478 } 1479 1480 /* 1481 * restore the in-core list back to the original state by adding 1482 * copy of original objects and deleted objects back to list 1483 */ 1484 insert_list_into_list(&(soft_slot.token_object_list), 1485 del_objs_list, end_del_objs_list); 1486 insert_list_into_list(&(soft_slot.token_object_list), 1487 copy_of_mod_objs_list, end_copy_of_mod_objs_list); 1488 1489 /* 1490 * remove the modified objects, and newly objects list 1491 */ 1492 delete_all_objs_in_list(mod_objs_list); 1493 delete_all_objs_in_list(added_objs_list); 1494 return (rv); 1495 } 1496