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 <md5.h> 27 #include <pthread.h> 28 #include <syslog.h> 29 #include <stdlib.h> 30 #include <string.h> 31 #include <strings.h> 32 #include <sys/sha1.h> 33 #include <security/cryptoki.h> 34 #include "softGlobal.h" 35 #include "softSession.h" 36 #include "softObject.h" 37 #include "softOps.h" 38 #include "softKeystore.h" 39 #include "softKeystoreUtil.h" 40 41 42 CK_ULONG soft_session_cnt = 0; /* the number of opened sessions */ 43 CK_ULONG soft_session_rw_cnt = 0; /* the number of opened R/W sessions */ 44 45 #define DIGEST_MECH_OK(_m_) ((_m_) == CKM_MD5 || (_m_) == CKM_SHA_1) 46 47 /* 48 * Delete all the sessions. First, obtain the global session 49 * list lock. Then start to delete one session at a time. 50 * Release the global session list lock before returning to 51 * caller. 52 */ 53 CK_RV 54 soft_delete_all_sessions(boolean_t force) 55 { 56 57 CK_RV rv = CKR_OK; 58 CK_RV rv1; 59 soft_session_t *session_p; 60 soft_session_t *session_p1; 61 62 /* Acquire the global session list lock */ 63 (void) pthread_mutex_lock(&soft_sessionlist_mutex); 64 65 session_p = soft_session_list; 66 67 /* Delete all the sessions in the session list */ 68 while (session_p) { 69 session_p1 = session_p->next; 70 71 /* 72 * Delete a session by calling soft_delete_session() 73 * with a session pointer and a boolean arguments. 74 * Boolean value TRUE is used to indicate that the 75 * caller holds the lock on the global session list. 76 * 77 */ 78 rv1 = soft_delete_session(session_p, force, B_TRUE); 79 80 /* Record the very first error code */ 81 if (rv == CKR_OK) { 82 rv = rv1; 83 } 84 85 session_p = session_p1; 86 } 87 88 /* No session left */ 89 soft_session_list = NULL; 90 91 /* Release the global session list lock */ 92 (void) pthread_mutex_unlock(&soft_sessionlist_mutex); 93 94 return (rv); 95 96 } 97 98 99 /* 100 * Create a new session struct, and add it to the session linked list. 101 * 102 * This function will acquire the global session list lock, and release 103 * it after adding the session to the session linked list. 104 */ 105 CK_RV 106 soft_add_session(CK_FLAGS flags, CK_VOID_PTR pApplication, 107 CK_NOTIFY notify, CK_ULONG *sessionhandle_p) 108 { 109 110 soft_session_t *new_sp = NULL; 111 112 /* Allocate a new session struct */ 113 new_sp = calloc(1, sizeof (soft_session_t)); 114 if (new_sp == NULL) { 115 return (CKR_HOST_MEMORY); 116 } 117 118 new_sp->magic_marker = SOFTTOKEN_SESSION_MAGIC; 119 new_sp->pApplication = pApplication; 120 new_sp->Notify = notify; 121 new_sp->flags = flags; 122 new_sp->state = CKS_RO_PUBLIC_SESSION; 123 new_sp->object_list = NULL; 124 new_sp->ses_refcnt = 0; 125 new_sp->ses_close_sync = 0; 126 127 (void) pthread_mutex_lock(&soft_giant_mutex); 128 if (soft_slot.authenticated) { 129 (void) pthread_mutex_unlock(&soft_giant_mutex); 130 if (flags & CKF_RW_SESSION) { 131 new_sp->state = CKS_RW_USER_FUNCTIONS; 132 } else { 133 new_sp->state = CKS_RO_USER_FUNCTIONS; 134 } 135 } else { 136 (void) pthread_mutex_unlock(&soft_giant_mutex); 137 if (flags & CKF_RW_SESSION) { 138 new_sp->state = CKS_RW_PUBLIC_SESSION; 139 } else { 140 new_sp->state = CKS_RO_PUBLIC_SESSION; 141 } 142 } 143 144 /* Initialize the lock for the newly created session */ 145 if (pthread_mutex_init(&new_sp->session_mutex, NULL) != 0) { 146 free(new_sp); 147 return (CKR_CANT_LOCK); 148 } 149 150 (void) pthread_cond_init(&new_sp->ses_free_cond, NULL); 151 152 /* Acquire the global session list lock */ 153 (void) pthread_mutex_lock(&soft_sessionlist_mutex); 154 155 /* Insert the new session in front of session list */ 156 if (soft_session_list == NULL) { 157 soft_session_list = new_sp; 158 new_sp->next = NULL; 159 new_sp->prev = NULL; 160 } else { 161 soft_session_list->prev = new_sp; 162 new_sp->next = soft_session_list; 163 new_sp->prev = NULL; 164 soft_session_list = new_sp; 165 } 166 167 /* Type casting the address of a session struct to a session handle */ 168 *sessionhandle_p = (CK_ULONG)new_sp; 169 ++soft_session_cnt; 170 if (flags & CKF_RW_SESSION) 171 ++soft_session_rw_cnt; 172 173 if (soft_session_cnt == 1) 174 /* 175 * This is the first session to be opened, so we can set 176 * validate the public token objects in token list now. 177 */ 178 soft_validate_token_objects(B_TRUE); 179 180 /* Release the global session list lock */ 181 (void) pthread_mutex_unlock(&soft_sessionlist_mutex); 182 183 return (CKR_OK); 184 185 } 186 187 /* 188 * This function adds the to-be-freed session to a linked list. 189 * When the number of sessions queued in the linked list reaches the 190 * maximum threshold MAX_SES_TO_BE_FREED, it will free the first 191 * session (FIFO) in the list. 192 */ 193 void 194 session_delay_free(soft_session_t *sp) 195 { 196 soft_session_t *tmp; 197 198 (void) pthread_mutex_lock(&ses_delay_freed.ses_to_be_free_mutex); 199 200 /* Add the newly deleted session at the end of the list */ 201 sp->next = NULL; 202 if (ses_delay_freed.first == NULL) { 203 ses_delay_freed.last = sp; 204 ses_delay_freed.first = sp; 205 } else { 206 ses_delay_freed.last->next = sp; 207 ses_delay_freed.last = sp; 208 } 209 210 if (++ses_delay_freed.count >= MAX_SES_TO_BE_FREED) { 211 /* 212 * Free the first session in the list only if 213 * the total count reaches maximum threshold. 214 */ 215 ses_delay_freed.count--; 216 tmp = ses_delay_freed.first->next; 217 free(ses_delay_freed.first); 218 ses_delay_freed.first = tmp; 219 } 220 (void) pthread_mutex_unlock(&ses_delay_freed.ses_to_be_free_mutex); 221 } 222 223 /* 224 * Delete a session: 225 * - Remove the session from the session linked list. 226 * Holding the lock on the global session list is needed to do this. 227 * - Release all the objects created by the session. 228 * 229 * The boolean argument lock_held is used to indicate that whether 230 * the caller of this function holds the lock on the global session 231 * list or not. 232 * - When called by soft_delete_all_sessions(), which is called by 233 * C_Finalize() or C_CloseAllSessions() -- the lock_held = TRUE. 234 * - When called by C_CloseSession() -- the lock_held = FALSE. 235 * 236 * When the caller does not hold the lock on the global session 237 * list, this function will acquire that lock in order to proceed, 238 * and also release that lock before returning to caller. 239 */ 240 CK_RV 241 soft_delete_session(soft_session_t *session_p, 242 boolean_t force, boolean_t lock_held) 243 { 244 245 /* 246 * Check to see if the caller holds the lock on the global 247 * session list. If not, we need to acquire that lock in 248 * order to proceed. 249 */ 250 if (!lock_held) { 251 /* Acquire the global session list lock */ 252 (void) pthread_mutex_lock(&soft_sessionlist_mutex); 253 } 254 255 /* 256 * Remove the session from the session linked list first. 257 */ 258 if (soft_session_list == session_p) { 259 /* Session is the first one in the list */ 260 if (session_p->next) { 261 soft_session_list = session_p->next; 262 session_p->next->prev = NULL; 263 } else { 264 /* Session is the only one in the list */ 265 soft_session_list = NULL; 266 } 267 } else { 268 /* Session is not the first one in the list */ 269 if (session_p->next) { 270 /* Session is in the middle of the list */ 271 session_p->prev->next = session_p->next; 272 session_p->next->prev = session_p->prev; 273 } else { 274 /* Session is the last one in the list */ 275 session_p->prev->next = NULL; 276 } 277 } 278 279 --soft_session_cnt; 280 if (session_p->flags & CKF_RW_SESSION) 281 --soft_session_rw_cnt; 282 283 if (!lock_held) { 284 /* 285 * If the global session list lock is obtained by 286 * this function, then release that lock after 287 * removing the session from session linked list. 288 * We want the releasing of the objects of the 289 * session, and freeing of the session itself to 290 * be done without holding the global session list 291 * lock. 292 */ 293 (void) pthread_mutex_unlock(&soft_sessionlist_mutex); 294 } 295 296 297 /* Acquire the individual session lock */ 298 (void) pthread_mutex_lock(&session_p->session_mutex); 299 /* 300 * Make sure another thread hasn't freed the session. 301 */ 302 if (session_p->magic_marker != SOFTTOKEN_SESSION_MAGIC) { 303 (void) pthread_mutex_unlock(&session_p->session_mutex); 304 return (CKR_OK); 305 } 306 307 /* 308 * The deletion of a session must be blocked when the session 309 * reference count is not zero. This means if any session related 310 * operation starts prior to the session close operation gets in, 311 * the session closing thread must wait for the non-closing 312 * operation to be completed before it can proceed the close 313 * operation. 314 * 315 * Unless we are being forced to shut everything down, this only 316 * happens if the libraries _fini() is running not of someone 317 * explicitly called C_Finalize(). 318 */ 319 if (force) 320 session_p->ses_refcnt = 0; 321 322 while (session_p->ses_refcnt != 0) { 323 /* 324 * We set the SESSION_REFCNT_WAITING flag before we put 325 * this closing thread in a wait state, so other non-closing 326 * operation thread will signal to wake it up only when 327 * the session reference count becomes zero and this flag 328 * is set. 329 */ 330 session_p->ses_close_sync |= SESSION_REFCNT_WAITING; 331 (void) pthread_cond_wait(&session_p->ses_free_cond, 332 &session_p->session_mutex); 333 } 334 335 session_p->ses_close_sync &= ~SESSION_REFCNT_WAITING; 336 337 /* 338 * Remove all the objects created in this session. 339 */ 340 soft_delete_all_objects_in_session(session_p); 341 342 /* 343 * Mark session as no longer valid. This can only be done after all 344 * objects created by this session are free'd since the marker is 345 * still needed in the process of removing objects from the session. 346 */ 347 session_p->magic_marker = 0; 348 349 (void) pthread_cond_destroy(&session_p->ses_free_cond); 350 351 /* In case application did not call Final */ 352 if (session_p->digest.context != NULL) 353 free(session_p->digest.context); 354 355 if (session_p->encrypt.context != NULL) 356 /* 357 * 1st B_TRUE: encrypt 358 * 2nd B_TRUE: caller is holding session_mutex. 359 */ 360 soft_crypt_cleanup(session_p, B_TRUE, B_TRUE); 361 362 if (session_p->decrypt.context != NULL) 363 /* 364 * 1st B_FALSE: decrypt 365 * 2nd B_TRUE: caller is holding session_mutex. 366 */ 367 soft_crypt_cleanup(session_p, B_FALSE, B_TRUE); 368 369 if (session_p->sign.context != NULL) 370 free(session_p->sign.context); 371 372 if (session_p->verify.context != NULL) 373 free(session_p->verify.context); 374 375 if (session_p->find_objects.context != NULL) { 376 find_context_t *fcontext; 377 fcontext = (find_context_t *)session_p->find_objects.context; 378 free(fcontext->objs_found); 379 free(fcontext); 380 } 381 382 /* Reset SESSION_IS_CLOSIN flag. */ 383 session_p->ses_close_sync &= ~SESSION_IS_CLOSING; 384 385 (void) pthread_mutex_unlock(&session_p->session_mutex); 386 /* Destroy the individual session lock */ 387 (void) pthread_mutex_destroy(&session_p->session_mutex); 388 389 /* Delay freeing the session */ 390 session_delay_free(session_p); 391 392 return (CKR_OK); 393 } 394 395 396 /* 397 * This function is used to type cast a session handle to a pointer to 398 * the session struct. Also, it does the following things: 399 * 1) Check to see if the session struct is tagged with a session 400 * magic number. This is to detect when an application passes 401 * a bogus session pointer. 402 * 2) Acquire the lock on the designated session. 403 * 3) Check to see if the session is in the closing state that another 404 * thread is performing. 405 * 4) Increment the session reference count by one. This is to prevent 406 * this session from being closed by other thread. 407 * 5) Release the lock held on the designated session. 408 */ 409 CK_RV 410 handle2session(CK_SESSION_HANDLE hSession, soft_session_t **session_p) 411 { 412 413 soft_session_t *sp = (soft_session_t *)(hSession); 414 415 /* 416 * No need to hold soft_sessionlist_mutex as we are 417 * just reading the value and 32-bit reads are atomic. 418 */ 419 if (all_sessions_closing) { 420 return (CKR_SESSION_CLOSED); 421 } 422 423 if ((sp == NULL) || 424 (sp->magic_marker != SOFTTOKEN_SESSION_MAGIC)) { 425 return (CKR_SESSION_HANDLE_INVALID); 426 } 427 (void) pthread_mutex_lock(&sp->session_mutex); 428 429 if (sp->ses_close_sync & SESSION_IS_CLOSING) { 430 (void) pthread_mutex_unlock(&sp->session_mutex); 431 return (CKR_SESSION_CLOSED); 432 } 433 434 /* Increment session ref count. */ 435 sp->ses_refcnt++; 436 437 (void) pthread_mutex_unlock(&sp->session_mutex); 438 439 *session_p = sp; 440 441 return (CKR_OK); 442 } 443 444 /* 445 * The format to be saved in the pOperationState will be: 446 * 1. internal_op_state_t 447 * 2. crypto_active_op_t 448 * 3. actual context of the active operation 449 */ 450 CK_RV 451 soft_get_operationstate(soft_session_t *session_p, CK_BYTE_PTR pOperationState, 452 CK_ULONG_PTR pulOperationStateLen) 453 { 454 455 internal_op_state_t *p_op_state; 456 CK_ULONG op_data_len = 0; 457 CK_RV rv = CKR_OK; 458 459 if (pulOperationStateLen == NULL) 460 return (CKR_ARGUMENTS_BAD); 461 462 (void) pthread_mutex_lock(&session_p->session_mutex); 463 464 /* Check to see if encrypt operation is active. */ 465 if (session_p->encrypt.flags & CRYPTO_OPERATION_ACTIVE) { 466 rv = CKR_STATE_UNSAVEABLE; 467 goto unlock_session; 468 } 469 470 /* Check to see if decrypt operation is active. */ 471 if (session_p->decrypt.flags & CRYPTO_OPERATION_ACTIVE) { 472 rv = CKR_STATE_UNSAVEABLE; 473 goto unlock_session; 474 } 475 476 /* Check to see if sign operation is active. */ 477 if (session_p->sign.flags & CRYPTO_OPERATION_ACTIVE) { 478 rv = CKR_STATE_UNSAVEABLE; 479 goto unlock_session; 480 } 481 482 /* Check to see if verify operation is active. */ 483 if (session_p->verify.flags & CRYPTO_OPERATION_ACTIVE) { 484 rv = CKR_STATE_UNSAVEABLE; 485 goto unlock_session; 486 } 487 488 /* Check to see if digest operation is active. */ 489 if (session_p->digest.flags & CRYPTO_OPERATION_ACTIVE) { 490 op_data_len = sizeof (internal_op_state_t) + 491 sizeof (crypto_active_op_t); 492 493 switch (session_p->digest.mech.mechanism) { 494 case CKM_MD5: 495 op_data_len += sizeof (MD5_CTX); 496 break; 497 case CKM_SHA_1: 498 op_data_len += sizeof (SHA1_CTX); 499 break; 500 default: 501 rv = CKR_STATE_UNSAVEABLE; 502 goto unlock_session; 503 } 504 505 if (pOperationState == NULL_PTR) { 506 *pulOperationStateLen = op_data_len; 507 goto unlock_session; 508 } else { 509 if (*pulOperationStateLen < op_data_len) { 510 *pulOperationStateLen = op_data_len; 511 rv = CKR_BUFFER_TOO_SMALL; 512 goto unlock_session; 513 } 514 } 515 516 /* Save internal_op_state_t */ 517 /* LINTED E_BAD_PTR_CAST_ALIGN */ 518 p_op_state = (internal_op_state_t *)pOperationState; 519 p_op_state->op_len = op_data_len; 520 p_op_state->op_active = DIGEST_OP; 521 p_op_state->op_session_state = session_p->state; 522 523 /* Save crypto_active_op_t */ 524 (void) memcpy((CK_BYTE *)pOperationState + 525 sizeof (internal_op_state_t), 526 &session_p->digest, 527 sizeof (crypto_active_op_t)); 528 529 switch (session_p->digest.mech.mechanism) { 530 case CKM_MD5: 531 /* Save MD5_CTX for the active digest operation */ 532 (void) memcpy((CK_BYTE *)pOperationState + 533 sizeof (internal_op_state_t) + 534 sizeof (crypto_active_op_t), 535 session_p->digest.context, 536 sizeof (MD5_CTX)); 537 break; 538 539 case CKM_SHA_1: 540 /* Save SHA1_CTX for the active digest operation */ 541 (void) memcpy((CK_BYTE *)pOperationState + 542 sizeof (internal_op_state_t) + 543 sizeof (crypto_active_op_t), 544 session_p->digest.context, 545 sizeof (SHA1_CTX)); 546 break; 547 548 default: 549 rv = CKR_STATE_UNSAVEABLE; 550 } 551 } else { 552 rv = CKR_OPERATION_NOT_INITIALIZED; 553 goto unlock_session; 554 } 555 556 *pulOperationStateLen = op_data_len; 557 558 unlock_session: 559 (void) pthread_mutex_unlock(&session_p->session_mutex); 560 561 return (rv); 562 563 } 564 565 static CK_BYTE_PTR alloc_digest(CK_ULONG mech) 566 { 567 CK_BYTE_PTR ret_val; 568 569 switch (mech) { 570 case CKM_MD5: 571 ret_val = (CK_BYTE_PTR) malloc(sizeof (MD5_CTX)); 572 break; 573 case CKM_SHA_1: 574 ret_val = (CK_BYTE_PTR) malloc(sizeof (SHA1_CTX)); 575 break; 576 default: ret_val = NULL; 577 } 578 579 return (ret_val); 580 } 581 582 /* 583 * The format to be restored from the pOperationState will be: 584 * 1. internal_op_state_t 585 * 2. crypto_active_op_t 586 * 3. actual context of the saved operation 587 */ 588 CK_RV 589 soft_set_operationstate(soft_session_t *session_p, CK_BYTE_PTR pOperationState, 590 CK_ULONG ulOperationStateLen, CK_OBJECT_HANDLE hEncryptionKey, 591 CK_OBJECT_HANDLE hAuthenticationKey) 592 { 593 594 CK_RV rv = CKR_OK; 595 internal_op_state_t *p_op_state; 596 crypto_active_op_t *p_active_op; 597 CK_ULONG offset = 0; 598 CK_ULONG mech; 599 void *free_it = NULL; 600 601 /* LINTED E_BAD_PTR_CAST_ALIGN */ 602 p_op_state = (internal_op_state_t *)pOperationState; 603 604 if (p_op_state->op_len != ulOperationStateLen) { 605 /* 606 * The supplied data length does not match with 607 * the saved data length. 608 */ 609 return (CKR_SAVED_STATE_INVALID); 610 } 611 612 if (p_op_state->op_active != DIGEST_OP) 613 return (CKR_SAVED_STATE_INVALID); 614 615 if ((hAuthenticationKey != 0) || (hEncryptionKey != 0)) { 616 return (CKR_KEY_NOT_NEEDED); 617 } 618 619 offset = sizeof (internal_op_state_t); 620 /* LINTED E_BAD_PTR_CAST_ALIGN */ 621 p_active_op = (crypto_active_op_t *)(pOperationState + offset); 622 offset += sizeof (crypto_active_op_t); 623 mech = p_active_op->mech.mechanism; 624 625 if (!DIGEST_MECH_OK(mech)) { 626 return (CKR_SAVED_STATE_INVALID); 627 } 628 629 /* 630 * We may reuse digest.context in case the digest mechanisms (the one, 631 * which belongs to session and the operation, which we are restoring) 632 * are the same. If digest mechanisms are different, we have to release 633 * the digest context, which belongs to session and allocate a new one. 634 */ 635 (void) pthread_mutex_lock(&session_p->session_mutex); 636 637 if (session_p->state != p_op_state->op_session_state) { 638 /* 639 * The supplied session state does not match with 640 * the saved session state. 641 */ 642 rv = CKR_SAVED_STATE_INVALID; 643 goto unlock_session; 644 } 645 646 if (session_p->digest.context && 647 (session_p->digest.mech.mechanism != mech)) { 648 free_it = session_p->digest.context; 649 session_p->digest.context = NULL; 650 } 651 652 if (session_p->digest.context == NULL) { 653 session_p->digest.context = alloc_digest(mech); 654 655 if (session_p->digest.context == NULL) { 656 /* 657 * put back original context into session in case 658 * allocation of new context has failed. 659 */ 660 session_p->digest.context = free_it; 661 free_it = NULL; 662 rv = CKR_HOST_MEMORY; 663 goto unlock_session; 664 } 665 } 666 667 /* Restore crypto_active_op_t */ 668 session_p->digest.mech.mechanism = mech; 669 session_p->digest.flags = p_active_op->flags; 670 671 switch (mech) { 672 case CKM_MD5: 673 /* Restore MD5_CTX from the saved digest operation */ 674 (void) memcpy((CK_BYTE *)session_p->digest.context, 675 (CK_BYTE *)pOperationState + offset, 676 sizeof (MD5_CTX)); 677 break; 678 case CKM_SHA_1: 679 /* Restore SHA1_CTX from the saved digest operation */ 680 (void) memcpy((CK_BYTE *)session_p->digest.context, 681 (CK_BYTE *)pOperationState + offset, 682 sizeof (SHA1_CTX)); 683 break; 684 default: 685 /* never reached */ 686 rv = CKR_SAVED_STATE_INVALID; 687 } 688 689 unlock_session: 690 (void) pthread_mutex_unlock(&session_p->session_mutex); 691 692 if (free_it != NULL) 693 free(free_it); 694 695 return (rv); 696 } 697 698 699 CK_RV 700 soft_login(CK_UTF8CHAR_PTR pPin, CK_ULONG ulPinLen) 701 { 702 703 /* 704 * Authenticate the input PIN. 705 */ 706 return (soft_verify_pin(pPin, ulPinLen)); 707 708 } 709 710 void 711 soft_logout(void) 712 { 713 714 /* 715 * Delete all the private token objects from the "token_object_list". 716 */ 717 soft_delete_all_in_core_token_objects(PRIVATE_TOKEN); 718 return; 719 720 } 721 722 void 723 soft_acquire_all_session_mutexes() 724 { 725 soft_session_t *session_p = soft_session_list; 726 727 /* Iterate through sessions acquiring all mutexes */ 728 while (session_p) { 729 (void) pthread_mutex_lock(&session_p->session_mutex); 730 session_p = session_p->next; 731 } 732 } 733 734 void 735 soft_release_all_session_mutexes() 736 { 737 soft_session_t *session_p = soft_session_list; 738 739 /* Iterate through sessions releasing all mutexes */ 740 while (session_p) { 741 /* 742 * N.B. Ideally, should go in opposite order to guarantee 743 * lock-order requirements but there is no tail pointer. 744 */ 745 (void) pthread_mutex_unlock(&session_p->session_mutex); 746 session_p = session_p->next; 747 } 748 } 749