1 /* $NetBSD: open.c,v 1.2 2020/08/11 13:15:37 christos Exp $ */ 2 3 /* $OpenLDAP$ */ 4 /* This work is part of OpenLDAP Software <http://www.openldap.org/>. 5 * 6 * Copyright 1998-2020 The OpenLDAP Foundation. 7 * All rights reserved. 8 * 9 * Redistribution and use in source and binary forms, with or without 10 * modification, are permitted only as authorized by the OpenLDAP 11 * Public License. 12 * 13 * A copy of this license is available in the file LICENSE in the 14 * top-level directory of the distribution or, alternatively, at 15 * <http://www.OpenLDAP.org/license.html>. 16 */ 17 /* Portions Copyright (c) 1995 Regents of the University of Michigan. 18 * All rights reserved. 19 */ 20 21 #include <sys/cdefs.h> 22 __RCSID("$NetBSD: open.c,v 1.2 2020/08/11 13:15:37 christos Exp $"); 23 24 #include "portable.h" 25 26 #include <stdio.h> 27 #ifdef HAVE_LIMITS_H 28 #include <limits.h> 29 #endif 30 31 #include <ac/stdlib.h> 32 33 #include <ac/param.h> 34 #include <ac/socket.h> 35 #include <ac/string.h> 36 #include <ac/time.h> 37 38 #include <ac/unistd.h> 39 40 #include "ldap-int.h" 41 #include "ldap.h" 42 #include "ldap_log.h" 43 44 /* Caller must hold the conn_mutex since simultaneous accesses are possible */ 45 int ldap_open_defconn( LDAP *ld ) 46 { 47 ld->ld_defconn = ldap_new_connection( ld, 48 &ld->ld_options.ldo_defludp, 1, 1, NULL, 0, 0 ); 49 50 if( ld->ld_defconn == NULL ) { 51 ld->ld_errno = LDAP_SERVER_DOWN; 52 return -1; 53 } 54 55 ++ld->ld_defconn->lconn_refcnt; /* so it never gets closed/freed */ 56 return 0; 57 } 58 59 /* 60 * ldap_open - initialize and connect to an ldap server. A magic cookie to 61 * be used for future communication is returned on success, NULL on failure. 62 * "host" may be a space-separated list of hosts or IP addresses 63 * 64 * Example: 65 * LDAP *ld; 66 * ld = ldap_open( hostname, port ); 67 */ 68 69 LDAP * 70 ldap_open( LDAP_CONST char *host, int port ) 71 { 72 int rc; 73 LDAP *ld; 74 75 Debug( LDAP_DEBUG_TRACE, "ldap_open(%s, %d)\n", 76 host, port, 0 ); 77 78 ld = ldap_init( host, port ); 79 if ( ld == NULL ) { 80 return( NULL ); 81 } 82 83 LDAP_MUTEX_LOCK( &ld->ld_conn_mutex ); 84 rc = ldap_open_defconn( ld ); 85 LDAP_MUTEX_UNLOCK( &ld->ld_conn_mutex ); 86 87 if( rc < 0 ) { 88 ldap_ld_free( ld, 0, NULL, NULL ); 89 ld = NULL; 90 } 91 92 Debug( LDAP_DEBUG_TRACE, "ldap_open: %s\n", 93 ld != NULL ? "succeeded" : "failed", 0, 0 ); 94 95 return ld; 96 } 97 98 99 100 int 101 ldap_create( LDAP **ldp ) 102 { 103 LDAP *ld; 104 struct ldapoptions *gopts; 105 106 *ldp = NULL; 107 /* Get pointer to global option structure */ 108 if ( (gopts = LDAP_INT_GLOBAL_OPT()) == NULL) { 109 return LDAP_NO_MEMORY; 110 } 111 112 /* Initialize the global options, if not already done. */ 113 if( gopts->ldo_valid != LDAP_INITIALIZED ) { 114 ldap_int_initialize(gopts, NULL); 115 if ( gopts->ldo_valid != LDAP_INITIALIZED ) 116 return LDAP_LOCAL_ERROR; 117 } 118 119 Debug( LDAP_DEBUG_TRACE, "ldap_create\n", 0, 0, 0 ); 120 121 if ( (ld = (LDAP *) LDAP_CALLOC( 1, sizeof(LDAP) )) == NULL ) { 122 return( LDAP_NO_MEMORY ); 123 } 124 125 if ( (ld->ldc = (struct ldap_common *) LDAP_CALLOC( 1, 126 sizeof(struct ldap_common) )) == NULL ) { 127 LDAP_FREE( (char *)ld ); 128 return( LDAP_NO_MEMORY ); 129 } 130 /* copy the global options */ 131 LDAP_MUTEX_LOCK( &gopts->ldo_mutex ); 132 AC_MEMCPY(&ld->ld_options, gopts, sizeof(ld->ld_options)); 133 #ifdef LDAP_R_COMPILE 134 /* Properly initialize the structs mutex */ 135 ldap_pvt_thread_mutex_init( &(ld->ld_ldopts_mutex) ); 136 #endif 137 LDAP_MUTEX_UNLOCK( &gopts->ldo_mutex ); 138 139 ld->ld_valid = LDAP_VALID_SESSION; 140 141 /* but not pointers to malloc'ed items */ 142 ld->ld_options.ldo_sctrls = NULL; 143 ld->ld_options.ldo_cctrls = NULL; 144 ld->ld_options.ldo_defludp = NULL; 145 ld->ld_options.ldo_conn_cbs = NULL; 146 147 #ifdef HAVE_CYRUS_SASL 148 ld->ld_options.ldo_def_sasl_mech = gopts->ldo_def_sasl_mech 149 ? LDAP_STRDUP( gopts->ldo_def_sasl_mech ) : NULL; 150 ld->ld_options.ldo_def_sasl_realm = gopts->ldo_def_sasl_realm 151 ? LDAP_STRDUP( gopts->ldo_def_sasl_realm ) : NULL; 152 ld->ld_options.ldo_def_sasl_authcid = gopts->ldo_def_sasl_authcid 153 ? LDAP_STRDUP( gopts->ldo_def_sasl_authcid ) : NULL; 154 ld->ld_options.ldo_def_sasl_authzid = gopts->ldo_def_sasl_authzid 155 ? LDAP_STRDUP( gopts->ldo_def_sasl_authzid ) : NULL; 156 #endif 157 158 #ifdef HAVE_TLS 159 /* We explicitly inherit the SSL_CTX, don't need the names/paths. Leave 160 * them empty to allow new SSL_CTX's to be created from scratch. 161 */ 162 memset( &ld->ld_options.ldo_tls_info, 0, 163 sizeof( ld->ld_options.ldo_tls_info )); 164 ld->ld_options.ldo_tls_ctx = NULL; 165 #endif 166 167 if ( gopts->ldo_defludp ) { 168 ld->ld_options.ldo_defludp = ldap_url_duplist(gopts->ldo_defludp); 169 170 if ( ld->ld_options.ldo_defludp == NULL ) goto nomem; 171 } 172 173 if (( ld->ld_selectinfo = ldap_new_select_info()) == NULL ) goto nomem; 174 175 ld->ld_lberoptions = LBER_USE_DER; 176 177 ld->ld_sb = ber_sockbuf_alloc( ); 178 if ( ld->ld_sb == NULL ) goto nomem; 179 180 #ifdef LDAP_R_COMPILE 181 ldap_pvt_thread_mutex_init( &ld->ld_msgid_mutex ); 182 ldap_pvt_thread_mutex_init( &ld->ld_conn_mutex ); 183 ldap_pvt_thread_mutex_init( &ld->ld_req_mutex ); 184 ldap_pvt_thread_mutex_init( &ld->ld_res_mutex ); 185 ldap_pvt_thread_mutex_init( &ld->ld_abandon_mutex ); 186 ldap_pvt_thread_mutex_init( &ld->ld_ldcmutex ); 187 #endif 188 ld->ld_ldcrefcnt = 1; 189 *ldp = ld; 190 return LDAP_SUCCESS; 191 192 nomem: 193 ldap_free_select_info( ld->ld_selectinfo ); 194 ldap_free_urllist( ld->ld_options.ldo_defludp ); 195 #ifdef HAVE_CYRUS_SASL 196 LDAP_FREE( ld->ld_options.ldo_def_sasl_authzid ); 197 LDAP_FREE( ld->ld_options.ldo_def_sasl_authcid ); 198 LDAP_FREE( ld->ld_options.ldo_def_sasl_realm ); 199 LDAP_FREE( ld->ld_options.ldo_def_sasl_mech ); 200 #endif 201 LDAP_FREE( (char *)ld ); 202 return LDAP_NO_MEMORY; 203 } 204 205 /* 206 * ldap_init - initialize the LDAP library. A magic cookie to be used for 207 * future communication is returned on success, NULL on failure. 208 * "host" may be a space-separated list of hosts or IP addresses 209 * 210 * Example: 211 * LDAP *ld; 212 * ld = ldap_init( host, port ); 213 */ 214 LDAP * 215 ldap_init( LDAP_CONST char *defhost, int defport ) 216 { 217 LDAP *ld; 218 int rc; 219 220 rc = ldap_create(&ld); 221 if ( rc != LDAP_SUCCESS ) 222 return NULL; 223 224 if (defport != 0) 225 ld->ld_options.ldo_defport = defport; 226 227 if (defhost != NULL) { 228 rc = ldap_set_option(ld, LDAP_OPT_HOST_NAME, defhost); 229 if ( rc != LDAP_SUCCESS ) { 230 ldap_ld_free(ld, 1, NULL, NULL); 231 return NULL; 232 } 233 } 234 235 return( ld ); 236 } 237 238 239 int 240 ldap_initialize( LDAP **ldp, LDAP_CONST char *url ) 241 { 242 int rc; 243 LDAP *ld; 244 245 *ldp = NULL; 246 rc = ldap_create(&ld); 247 if ( rc != LDAP_SUCCESS ) 248 return rc; 249 250 if (url != NULL) { 251 rc = ldap_set_option(ld, LDAP_OPT_URI, url); 252 if ( rc != LDAP_SUCCESS ) { 253 ldap_ld_free(ld, 1, NULL, NULL); 254 return rc; 255 } 256 #ifdef LDAP_CONNECTIONLESS 257 if (ldap_is_ldapc_url(url)) 258 LDAP_IS_UDP(ld) = 1; 259 #endif 260 } 261 262 *ldp = ld; 263 return LDAP_SUCCESS; 264 } 265 266 int 267 ldap_init_fd( 268 ber_socket_t fd, 269 int proto, 270 LDAP_CONST char *url, 271 LDAP **ldp 272 ) 273 { 274 int rc; 275 LDAP *ld; 276 LDAPConn *conn; 277 #ifdef LDAP_CONNECTIONLESS 278 ber_socklen_t len; 279 #endif 280 281 *ldp = NULL; 282 rc = ldap_create( &ld ); 283 if( rc != LDAP_SUCCESS ) 284 return( rc ); 285 286 if (url != NULL) { 287 rc = ldap_set_option(ld, LDAP_OPT_URI, url); 288 if ( rc != LDAP_SUCCESS ) { 289 ldap_ld_free(ld, 1, NULL, NULL); 290 return rc; 291 } 292 } 293 294 LDAP_MUTEX_LOCK( &ld->ld_conn_mutex ); 295 /* Attach the passed socket as the LDAP's connection */ 296 conn = ldap_new_connection( ld, NULL, 1, 0, NULL, 0, 0 ); 297 if( conn == NULL ) { 298 ldap_unbind_ext( ld, NULL, NULL ); 299 return( LDAP_NO_MEMORY ); 300 } 301 if( url ) 302 conn->lconn_server = ldap_url_dup( ld->ld_options.ldo_defludp ); 303 ber_sockbuf_ctrl( conn->lconn_sb, LBER_SB_OPT_SET_FD, &fd ); 304 ld->ld_defconn = conn; 305 ++ld->ld_defconn->lconn_refcnt; /* so it never gets closed/freed */ 306 LDAP_MUTEX_UNLOCK( &ld->ld_conn_mutex ); 307 308 switch( proto ) { 309 case LDAP_PROTO_TCP: 310 #ifdef LDAP_DEBUG 311 ber_sockbuf_add_io( conn->lconn_sb, &ber_sockbuf_io_debug, 312 LBER_SBIOD_LEVEL_PROVIDER, (void *)"tcp_" ); 313 #endif 314 ber_sockbuf_add_io( conn->lconn_sb, &ber_sockbuf_io_tcp, 315 LBER_SBIOD_LEVEL_PROVIDER, NULL ); 316 break; 317 318 #ifdef LDAP_CONNECTIONLESS 319 case LDAP_PROTO_UDP: 320 LDAP_IS_UDP(ld) = 1; 321 if( ld->ld_options.ldo_peer ) 322 ldap_memfree( ld->ld_options.ldo_peer ); 323 ld->ld_options.ldo_peer = ldap_memcalloc( 1, sizeof( struct sockaddr_storage ) ); 324 len = sizeof( struct sockaddr_storage ); 325 if( getpeername ( fd, ld->ld_options.ldo_peer, &len ) < 0) { 326 ldap_unbind_ext( ld, NULL, NULL ); 327 return( AC_SOCKET_ERROR ); 328 } 329 #ifdef LDAP_DEBUG 330 ber_sockbuf_add_io( conn->lconn_sb, &ber_sockbuf_io_debug, 331 LBER_SBIOD_LEVEL_PROVIDER, (void *)"udp_" ); 332 #endif 333 ber_sockbuf_add_io( conn->lconn_sb, &ber_sockbuf_io_udp, 334 LBER_SBIOD_LEVEL_PROVIDER, NULL ); 335 ber_sockbuf_add_io( conn->lconn_sb, &ber_sockbuf_io_readahead, 336 LBER_SBIOD_LEVEL_PROVIDER, NULL ); 337 break; 338 #endif /* LDAP_CONNECTIONLESS */ 339 340 case LDAP_PROTO_IPC: 341 #ifdef LDAP_DEBUG 342 ber_sockbuf_add_io( conn->lconn_sb, &ber_sockbuf_io_debug, 343 LBER_SBIOD_LEVEL_PROVIDER, (void *)"ipc_" ); 344 #endif 345 ber_sockbuf_add_io( conn->lconn_sb, &ber_sockbuf_io_fd, 346 LBER_SBIOD_LEVEL_PROVIDER, NULL ); 347 break; 348 349 case LDAP_PROTO_EXT: 350 /* caller must supply sockbuf handlers */ 351 break; 352 353 default: 354 ldap_unbind_ext( ld, NULL, NULL ); 355 return LDAP_PARAM_ERROR; 356 } 357 358 #ifdef LDAP_DEBUG 359 ber_sockbuf_add_io( conn->lconn_sb, &ber_sockbuf_io_debug, 360 INT_MAX, (void *)"ldap_" ); 361 #endif 362 363 /* Add the connection to the *LDAP's select pool */ 364 ldap_mark_select_read( ld, conn->lconn_sb ); 365 366 *ldp = ld; 367 return LDAP_SUCCESS; 368 } 369 370 /* Protected by ld_conn_mutex */ 371 int 372 ldap_int_open_connection( 373 LDAP *ld, 374 LDAPConn *conn, 375 LDAPURLDesc *srv, 376 int async ) 377 { 378 int rc = -1; 379 int proto; 380 381 Debug( LDAP_DEBUG_TRACE, "ldap_int_open_connection\n", 0, 0, 0 ); 382 383 switch ( proto = ldap_pvt_url_scheme2proto( srv->lud_scheme ) ) { 384 case LDAP_PROTO_TCP: 385 rc = ldap_connect_to_host( ld, conn->lconn_sb, 386 proto, srv, async ); 387 388 if ( rc == -1 ) return rc; 389 #ifdef LDAP_DEBUG 390 ber_sockbuf_add_io( conn->lconn_sb, &ber_sockbuf_io_debug, 391 LBER_SBIOD_LEVEL_PROVIDER, (void *)"tcp_" ); 392 #endif 393 ber_sockbuf_add_io( conn->lconn_sb, &ber_sockbuf_io_tcp, 394 LBER_SBIOD_LEVEL_PROVIDER, NULL ); 395 396 break; 397 398 #ifdef LDAP_CONNECTIONLESS 399 case LDAP_PROTO_UDP: 400 LDAP_IS_UDP(ld) = 1; 401 rc = ldap_connect_to_host( ld, conn->lconn_sb, 402 proto, srv, async ); 403 404 if ( rc == -1 ) return rc; 405 #ifdef LDAP_DEBUG 406 ber_sockbuf_add_io( conn->lconn_sb, &ber_sockbuf_io_debug, 407 LBER_SBIOD_LEVEL_PROVIDER, (void *)"udp_" ); 408 #endif 409 ber_sockbuf_add_io( conn->lconn_sb, &ber_sockbuf_io_udp, 410 LBER_SBIOD_LEVEL_PROVIDER, NULL ); 411 412 ber_sockbuf_add_io( conn->lconn_sb, &ber_sockbuf_io_readahead, 413 LBER_SBIOD_LEVEL_PROVIDER, NULL ); 414 415 break; 416 #endif 417 case LDAP_PROTO_IPC: 418 #ifdef LDAP_PF_LOCAL 419 /* only IPC mechanism supported is PF_LOCAL (PF_UNIX) */ 420 rc = ldap_connect_to_path( ld, conn->lconn_sb, 421 srv, async ); 422 if ( rc == -1 ) return rc; 423 #ifdef LDAP_DEBUG 424 ber_sockbuf_add_io( conn->lconn_sb, &ber_sockbuf_io_debug, 425 LBER_SBIOD_LEVEL_PROVIDER, (void *)"ipc_" ); 426 #endif 427 ber_sockbuf_add_io( conn->lconn_sb, &ber_sockbuf_io_fd, 428 LBER_SBIOD_LEVEL_PROVIDER, NULL ); 429 430 break; 431 #endif /* LDAP_PF_LOCAL */ 432 default: 433 return -1; 434 break; 435 } 436 437 conn->lconn_created = time( NULL ); 438 439 #ifdef LDAP_DEBUG 440 ber_sockbuf_add_io( conn->lconn_sb, &ber_sockbuf_io_debug, 441 INT_MAX, (void *)"ldap_" ); 442 #endif 443 444 #ifdef LDAP_CONNECTIONLESS 445 if( proto == LDAP_PROTO_UDP ) return 0; 446 #endif 447 448 #ifdef HAVE_TLS 449 if ((rc == 0 || rc == -2) && ( ld->ld_options.ldo_tls_mode == LDAP_OPT_X_TLS_HARD || 450 strcmp( srv->lud_scheme, "ldaps" ) == 0 )) 451 { 452 ++conn->lconn_refcnt; /* avoid premature free */ 453 454 rc = ldap_int_tls_start( ld, conn, srv ); 455 456 --conn->lconn_refcnt; 457 458 if (rc != LDAP_SUCCESS) { 459 /* process connection callbacks */ 460 { 461 struct ldapoptions *lo; 462 ldaplist *ll; 463 ldap_conncb *cb; 464 465 lo = &ld->ld_options; 466 LDAP_MUTEX_LOCK( &lo->ldo_mutex ); 467 if ( lo->ldo_conn_cbs ) { 468 for ( ll=lo->ldo_conn_cbs; ll; ll=ll->ll_next ) { 469 cb = ll->ll_data; 470 cb->lc_del( ld, conn->lconn_sb, cb ); 471 } 472 } 473 LDAP_MUTEX_UNLOCK( &lo->ldo_mutex ); 474 lo = LDAP_INT_GLOBAL_OPT(); 475 LDAP_MUTEX_LOCK( &lo->ldo_mutex ); 476 if ( lo->ldo_conn_cbs ) { 477 for ( ll=lo->ldo_conn_cbs; ll; ll=ll->ll_next ) { 478 cb = ll->ll_data; 479 cb->lc_del( ld, conn->lconn_sb, cb ); 480 } 481 } 482 LDAP_MUTEX_UNLOCK( &lo->ldo_mutex ); 483 } 484 ber_int_sb_close( conn->lconn_sb ); 485 return -1; 486 } 487 } 488 #endif 489 490 return( 0 ); 491 } 492 493 /* 494 * ldap_open_internal_connection - open connection and set file descriptor 495 * 496 * note: ldap_init_fd() may be preferable 497 */ 498 499 int 500 ldap_open_internal_connection( LDAP **ldp, ber_socket_t *fdp ) 501 { 502 int rc; 503 LDAPConn *c; 504 LDAPRequest *lr; 505 LDAP *ld; 506 507 rc = ldap_create( &ld ); 508 if( rc != LDAP_SUCCESS ) { 509 *ldp = NULL; 510 return( rc ); 511 } 512 513 /* Make it appear that a search request, msgid 0, was sent */ 514 lr = (LDAPRequest *)LDAP_CALLOC( 1, sizeof( LDAPRequest )); 515 if( lr == NULL ) { 516 ldap_unbind_ext( ld, NULL, NULL ); 517 *ldp = NULL; 518 return( LDAP_NO_MEMORY ); 519 } 520 memset(lr, 0, sizeof( LDAPRequest )); 521 lr->lr_msgid = 0; 522 lr->lr_status = LDAP_REQST_INPROGRESS; 523 lr->lr_res_errno = LDAP_SUCCESS; 524 /* no mutex lock needed, we just created this ld here */ 525 ld->ld_requests = lr; 526 527 LDAP_MUTEX_LOCK( &ld->ld_conn_mutex ); 528 /* Attach the passed socket as the *LDAP's connection */ 529 c = ldap_new_connection( ld, NULL, 1, 0, NULL, 0, 0 ); 530 if( c == NULL ) { 531 ldap_unbind_ext( ld, NULL, NULL ); 532 *ldp = NULL; 533 LDAP_MUTEX_UNLOCK( &ld->ld_conn_mutex ); 534 return( LDAP_NO_MEMORY ); 535 } 536 ber_sockbuf_ctrl( c->lconn_sb, LBER_SB_OPT_SET_FD, fdp ); 537 #ifdef LDAP_DEBUG 538 ber_sockbuf_add_io( c->lconn_sb, &ber_sockbuf_io_debug, 539 LBER_SBIOD_LEVEL_PROVIDER, (void *)"int_" ); 540 #endif 541 ber_sockbuf_add_io( c->lconn_sb, &ber_sockbuf_io_tcp, 542 LBER_SBIOD_LEVEL_PROVIDER, NULL ); 543 ld->ld_defconn = c; 544 LDAP_MUTEX_UNLOCK( &ld->ld_conn_mutex ); 545 546 /* Add the connection to the *LDAP's select pool */ 547 ldap_mark_select_read( ld, c->lconn_sb ); 548 549 /* Make this connection an LDAP V3 protocol connection */ 550 rc = LDAP_VERSION3; 551 ldap_set_option( ld, LDAP_OPT_PROTOCOL_VERSION, &rc ); 552 *ldp = ld; 553 554 ++ld->ld_defconn->lconn_refcnt; /* so it never gets closed/freed */ 555 556 return( LDAP_SUCCESS ); 557 } 558 559 LDAP * 560 ldap_dup( LDAP *old ) 561 { 562 LDAP *ld; 563 564 if ( old == NULL ) { 565 return( NULL ); 566 } 567 568 Debug( LDAP_DEBUG_TRACE, "ldap_dup\n", 0, 0, 0 ); 569 570 if ( (ld = (LDAP *) LDAP_CALLOC( 1, sizeof(LDAP) )) == NULL ) { 571 return( NULL ); 572 } 573 574 LDAP_MUTEX_LOCK( &old->ld_ldcmutex ); 575 ld->ldc = old->ldc; 576 old->ld_ldcrefcnt++; 577 LDAP_MUTEX_UNLOCK( &old->ld_ldcmutex ); 578 return ( ld ); 579 } 580 581 int 582 ldap_int_check_async_open( LDAP *ld, ber_socket_t sd ) 583 { 584 struct timeval tv = { 0 }; 585 int rc; 586 587 rc = ldap_int_poll( ld, sd, &tv, 1 ); 588 switch ( rc ) { 589 case 0: 590 /* now ready to start tls */ 591 ld->ld_defconn->lconn_status = LDAP_CONNST_CONNECTED; 592 break; 593 594 default: 595 ld->ld_errno = LDAP_CONNECT_ERROR; 596 return -1; 597 598 case -2: 599 /* connect not completed yet */ 600 ld->ld_errno = LDAP_X_CONNECTING; 601 return rc; 602 } 603 604 #ifdef HAVE_TLS 605 if ( ld->ld_options.ldo_tls_mode == LDAP_OPT_X_TLS_HARD || 606 !strcmp( ld->ld_defconn->lconn_server->lud_scheme, "ldaps" )) { 607 608 ++ld->ld_defconn->lconn_refcnt; /* avoid premature free */ 609 610 rc = ldap_int_tls_start( ld, ld->ld_defconn, ld->ld_defconn->lconn_server ); 611 612 --ld->ld_defconn->lconn_refcnt; 613 } 614 #endif 615 return rc; 616 } 617