1 /* 2 * WLDAP32 - LDAP support for Wine 3 * 4 * Copyright 2005 Hans Leidekker 5 * 6 * This library is free software; you can redistribute it and/or 7 * modify it under the terms of the GNU Lesser General Public 8 * License as published by the Free Software Foundation; either 9 * version 2.1 of the License, or (at your option) any later version. 10 * 11 * This library is distributed in the hope that it will be useful, 12 * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 14 * Lesser General Public License for more details. 15 * 16 * You should have received a copy of the GNU Lesser General Public 17 * License along with this library; if not, write to the Free Software 18 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA 19 */ 20 21 #include "config.h" 22 #include "wine/port.h" 23 24 #include <stdarg.h> 25 #ifdef HAVE_LDAP_H 26 #include <ldap.h> 27 #endif 28 29 #include "windef.h" 30 #include "winbase.h" 31 #include "winnls.h" 32 33 #include "winldap_private.h" 34 #include "wldap32.h" 35 #include "wine/debug.h" 36 37 WINE_DEFAULT_DEBUG_CHANNEL(wldap32); 38 39 /*********************************************************************** 40 * ldap_bindA (WLDAP32.@) 41 * 42 * See ldap_bindW. 43 */ 44 ULONG CDECL ldap_bindA( WLDAP32_LDAP *ld, PCHAR dn, PCHAR cred, ULONG method ) 45 { 46 ULONG ret = WLDAP32_LDAP_NOT_SUPPORTED; 47 #ifdef HAVE_LDAP 48 WCHAR *dnW = NULL, *credW = NULL; 49 50 ret = WLDAP32_LDAP_NO_MEMORY; 51 52 TRACE( "(%p, %s, %p, 0x%08x)\n", ld, debugstr_a(dn), cred, method ); 53 54 if (!ld) return ~0u; 55 56 if (dn) { 57 dnW = strAtoW( dn ); 58 if (!dnW) goto exit; 59 } 60 if (cred) { 61 credW = strAtoW( cred ); 62 if (!credW) goto exit; 63 } 64 65 ret = ldap_bindW( ld, dnW, credW, method ); 66 67 exit: 68 strfreeW( dnW ); 69 strfreeW( credW ); 70 71 #endif 72 return ret; 73 } 74 75 /*********************************************************************** 76 * ldap_bindW (WLDAP32.@) 77 * 78 * Authenticate with an LDAP server (asynchronous operation). 79 * 80 * PARAMS 81 * ld [I] Pointer to an LDAP context. 82 * dn [I] DN of entry to bind as. 83 * cred [I] Credentials (e.g. password string). 84 * method [I] Authentication method. 85 * 86 * RETURNS 87 * Success: Message ID of the bind operation. 88 * Failure: An LDAP error code. 89 * 90 * NOTES 91 * Only LDAP_AUTH_SIMPLE is supported (just like native). 92 */ 93 ULONG CDECL ldap_bindW( WLDAP32_LDAP *ld, PWCHAR dn, PWCHAR cred, ULONG method ) 94 { 95 ULONG ret = WLDAP32_LDAP_NOT_SUPPORTED; 96 #ifdef HAVE_LDAP 97 char *dnU = NULL, *credU = NULL; 98 struct berval pwd = { 0, NULL }; 99 int msg; 100 101 ret = WLDAP32_LDAP_NO_MEMORY; 102 103 TRACE( "(%p, %s, %p, 0x%08x)\n", ld, debugstr_w(dn), cred, method ); 104 105 if (!ld) return ~0u; 106 if (method != LDAP_AUTH_SIMPLE) return WLDAP32_LDAP_PARAM_ERROR; 107 108 if (dn) { 109 dnU = strWtoU( dn ); 110 if (!dnU) goto exit; 111 } 112 if (cred) { 113 credU = strWtoU( cred ); 114 if (!credU) goto exit; 115 116 pwd.bv_len = strlen( credU ); 117 pwd.bv_val = credU; 118 } 119 120 ret = ldap_sasl_bind( ld, dnU, LDAP_SASL_SIMPLE, &pwd, NULL, NULL, &msg ); 121 122 if (ret == LDAP_SUCCESS) 123 ret = msg; 124 else 125 ret = ~0u; 126 127 exit: 128 strfreeU( dnU ); 129 strfreeU( credU ); 130 131 #endif 132 return ret; 133 } 134 135 /*********************************************************************** 136 * ldap_bind_sA (WLDAP32.@) 137 * 138 * See ldap_bind_sW. 139 */ 140 ULONG CDECL ldap_bind_sA( WLDAP32_LDAP *ld, PCHAR dn, PCHAR cred, ULONG method ) 141 { 142 ULONG ret = WLDAP32_LDAP_NOT_SUPPORTED; 143 #ifdef HAVE_LDAP 144 WCHAR *dnW = NULL, *credW = NULL; 145 146 ret = WLDAP32_LDAP_NO_MEMORY; 147 148 TRACE( "(%p, %s, %p, 0x%08x)\n", ld, debugstr_a(dn), cred, method ); 149 150 if (!ld) return WLDAP32_LDAP_PARAM_ERROR; 151 152 if (dn) { 153 dnW = strAtoW( dn ); 154 if (!dnW) goto exit; 155 } 156 if (cred) { 157 credW = strAtoW( cred ); 158 if (!credW) goto exit; 159 } 160 161 ret = ldap_bind_sW( ld, dnW, credW, method ); 162 163 exit: 164 strfreeW( dnW ); 165 strfreeW( credW ); 166 167 #endif 168 return ret; 169 } 170 171 /*********************************************************************** 172 * ldap_bind_sW (WLDAP32.@) 173 * 174 * Authenticate with an LDAP server (synchronous operation). 175 * 176 * PARAMS 177 * ld [I] Pointer to an LDAP context. 178 * dn [I] DN of entry to bind as. 179 * cred [I] Credentials (e.g. password string). 180 * method [I] Authentication method. 181 * 182 * RETURNS 183 * Success: LDAP_SUCCESS 184 * Failure: An LDAP error code. 185 */ 186 ULONG CDECL ldap_bind_sW( WLDAP32_LDAP *ld, PWCHAR dn, PWCHAR cred, ULONG method ) 187 { 188 ULONG ret = WLDAP32_LDAP_NOT_SUPPORTED; 189 #ifdef HAVE_LDAP 190 char *dnU = NULL, *credU = NULL; 191 struct berval pwd = { 0, NULL }; 192 193 ret = WLDAP32_LDAP_NO_MEMORY; 194 195 TRACE( "(%p, %s, %p, 0x%08x)\n", ld, debugstr_w(dn), cred, method ); 196 197 if (!ld) return WLDAP32_LDAP_PARAM_ERROR; 198 if (method != LDAP_AUTH_SIMPLE) return WLDAP32_LDAP_PARAM_ERROR; 199 200 if (dn) { 201 dnU = strWtoU( dn ); 202 if (!dnU) goto exit; 203 } 204 if (cred) { 205 credU = strWtoU( cred ); 206 if (!credU) goto exit; 207 208 pwd.bv_len = strlen( credU ); 209 pwd.bv_val = credU; 210 } 211 212 ret = map_error( ldap_sasl_bind_s( ld, dnU, LDAP_SASL_SIMPLE, &pwd, NULL, NULL, NULL )); 213 214 exit: 215 strfreeU( dnU ); 216 strfreeU( credU ); 217 218 #endif 219 return ret; 220 } 221 222 /*********************************************************************** 223 * ldap_sasl_bindA (WLDAP32.@) 224 * 225 * See ldap_sasl_bindW. 226 */ 227 ULONG CDECL ldap_sasl_bindA( WLDAP32_LDAP *ld, const PCHAR dn, 228 const PCHAR mechanism, const BERVAL *cred, PLDAPControlA *serverctrls, 229 PLDAPControlA *clientctrls, int *message ) 230 { 231 ULONG ret = WLDAP32_LDAP_NOT_SUPPORTED; 232 #ifdef HAVE_LDAP 233 WCHAR *dnW, *mechanismW = NULL; 234 LDAPControlW **serverctrlsW = NULL, **clientctrlsW = NULL; 235 236 ret = WLDAP32_LDAP_NO_MEMORY; 237 238 TRACE( "(%p, %s, %s, %p, %p, %p, %p)\n", ld, debugstr_a(dn), 239 debugstr_a(mechanism), cred, serverctrls, clientctrls, message ); 240 241 if (!ld || !dn || !mechanism || !cred || !message) 242 return WLDAP32_LDAP_PARAM_ERROR; 243 244 dnW = strAtoW( dn ); 245 if (!dnW) goto exit; 246 247 mechanismW = strAtoW( mechanism ); 248 if (!mechanismW) goto exit; 249 250 if (serverctrls) { 251 serverctrlsW = controlarrayAtoW( serverctrls ); 252 if (!serverctrlsW) goto exit; 253 } 254 if (clientctrls) { 255 clientctrlsW = controlarrayAtoW( clientctrls ); 256 if (!clientctrlsW) goto exit; 257 } 258 259 ret = ldap_sasl_bindW( ld, dnW, mechanismW, cred, serverctrlsW, clientctrlsW, message ); 260 261 exit: 262 strfreeW( dnW ); 263 strfreeW( mechanismW ); 264 controlarrayfreeW( serverctrlsW ); 265 controlarrayfreeW( clientctrlsW ); 266 267 #endif 268 return ret; 269 } 270 271 /*********************************************************************** 272 * ldap_sasl_bindW (WLDAP32.@) 273 * 274 * Authenticate with an LDAP server using SASL (asynchronous operation). 275 * 276 * PARAMS 277 * ld [I] Pointer to an LDAP context. 278 * dn [I] DN of entry to bind as. 279 * mechanism [I] Authentication method. 280 * cred [I] Credentials. 281 * serverctrls [I] Array of LDAP server controls. 282 * clientctrls [I] Array of LDAP client controls. 283 * message [O] Message ID of the bind operation. 284 * 285 * RETURNS 286 * Success: LDAP_SUCCESS 287 * Failure: An LDAP error code. 288 * 289 * NOTES 290 * The serverctrls and clientctrls parameters are optional and should 291 * be set to NULL if not used. 292 */ 293 ULONG CDECL ldap_sasl_bindW( WLDAP32_LDAP *ld, const PWCHAR dn, 294 const PWCHAR mechanism, const BERVAL *cred, PLDAPControlW *serverctrls, 295 PLDAPControlW *clientctrls, int *message ) 296 { 297 ULONG ret = WLDAP32_LDAP_NOT_SUPPORTED; 298 #ifdef HAVE_LDAP 299 char *dnU, *mechanismU = NULL; 300 LDAPControl **serverctrlsU = NULL, **clientctrlsU = NULL; 301 struct berval credU; 302 303 ret = WLDAP32_LDAP_NO_MEMORY; 304 305 TRACE( "(%p, %s, %s, %p, %p, %p, %p)\n", ld, debugstr_w(dn), 306 debugstr_w(mechanism), cred, serverctrls, clientctrls, message ); 307 308 if (!ld || !dn || !mechanism || !cred || !message) 309 return WLDAP32_LDAP_PARAM_ERROR; 310 311 dnU = strWtoU( dn ); 312 if (!dnU) goto exit; 313 314 mechanismU = strWtoU( mechanism ); 315 if (!mechanismU) goto exit; 316 317 if (serverctrls) { 318 serverctrlsU = controlarrayWtoU( serverctrls ); 319 if (!serverctrlsU) goto exit; 320 } 321 if (clientctrls) { 322 clientctrlsU = controlarrayWtoU( clientctrls ); 323 if (!clientctrlsU) goto exit; 324 } 325 326 credU.bv_len = cred->bv_len; 327 credU.bv_val = cred->bv_val; 328 329 ret = map_error( ldap_sasl_bind( ld, dnU, mechanismU, &credU, 330 serverctrlsU, clientctrlsU, message )); 331 332 exit: 333 strfreeU( dnU ); 334 strfreeU( mechanismU ); 335 controlarrayfreeU( serverctrlsU ); 336 controlarrayfreeU( clientctrlsU ); 337 338 #endif 339 return ret; 340 } 341 342 /*********************************************************************** 343 * ldap_sasl_bind_sA (WLDAP32.@) 344 * 345 * See ldap_sasl_bind_sW. 346 */ 347 ULONG CDECL ldap_sasl_bind_sA( WLDAP32_LDAP *ld, const PCHAR dn, 348 const PCHAR mechanism, const BERVAL *cred, PLDAPControlA *serverctrls, 349 PLDAPControlA *clientctrls, PBERVAL *serverdata ) 350 { 351 ULONG ret = WLDAP32_LDAP_NOT_SUPPORTED; 352 #ifdef HAVE_LDAP 353 WCHAR *dnW, *mechanismW = NULL; 354 LDAPControlW **serverctrlsW = NULL, **clientctrlsW = NULL; 355 356 ret = WLDAP32_LDAP_NO_MEMORY; 357 358 TRACE( "(%p, %s, %s, %p, %p, %p, %p)\n", ld, debugstr_a(dn), 359 debugstr_a(mechanism), cred, serverctrls, clientctrls, serverdata ); 360 361 if (!ld || !dn || !mechanism || !cred || !serverdata) 362 return WLDAP32_LDAP_PARAM_ERROR; 363 364 dnW = strAtoW( dn ); 365 if (!dnW) goto exit; 366 367 mechanismW = strAtoW( mechanism ); 368 if (!mechanismW) goto exit; 369 370 if (serverctrls) { 371 serverctrlsW = controlarrayAtoW( serverctrls ); 372 if (!serverctrlsW) goto exit; 373 } 374 if (clientctrls) { 375 clientctrlsW = controlarrayAtoW( clientctrls ); 376 if (!clientctrlsW) goto exit; 377 } 378 379 ret = ldap_sasl_bind_sW( ld, dnW, mechanismW, cred, serverctrlsW, clientctrlsW, serverdata ); 380 381 exit: 382 strfreeW( dnW ); 383 strfreeW( mechanismW ); 384 controlarrayfreeW( serverctrlsW ); 385 controlarrayfreeW( clientctrlsW ); 386 387 #endif 388 return ret; 389 } 390 391 /*********************************************************************** 392 * ldap_sasl_bind_sW (WLDAP32.@) 393 * 394 * Authenticate with an LDAP server using SASL (synchronous operation). 395 * 396 * PARAMS 397 * ld [I] Pointer to an LDAP context. 398 * dn [I] DN of entry to bind as. 399 * mechanism [I] Authentication method. 400 * cred [I] Credentials. 401 * serverctrls [I] Array of LDAP server controls. 402 * clientctrls [I] Array of LDAP client controls. 403 * serverdata [O] Authentication response from the server. 404 * 405 * RETURNS 406 * Success: LDAP_SUCCESS 407 * Failure: An LDAP error code. 408 * 409 * NOTES 410 * The serverctrls and clientctrls parameters are optional and should 411 * be set to NULL if not used. 412 */ 413 ULONG CDECL ldap_sasl_bind_sW( WLDAP32_LDAP *ld, const PWCHAR dn, 414 const PWCHAR mechanism, const BERVAL *cred, PLDAPControlW *serverctrls, 415 PLDAPControlW *clientctrls, PBERVAL *serverdata ) 416 { 417 ULONG ret = WLDAP32_LDAP_NOT_SUPPORTED; 418 #ifdef HAVE_LDAP 419 char *dnU, *mechanismU = NULL; 420 LDAPControl **serverctrlsU = NULL, **clientctrlsU = NULL; 421 struct berval credU; 422 423 ret = WLDAP32_LDAP_NO_MEMORY; 424 425 TRACE( "(%p, %s, %s, %p, %p, %p, %p)\n", ld, debugstr_w(dn), 426 debugstr_w(mechanism), cred, serverctrls, clientctrls, serverdata ); 427 428 if (!ld || !dn || !mechanism || !cred || !serverdata) 429 return WLDAP32_LDAP_PARAM_ERROR; 430 431 dnU = strWtoU( dn ); 432 if (!dnU) goto exit; 433 434 mechanismU = strWtoU( mechanism ); 435 if (!mechanismU) goto exit; 436 437 if (serverctrls) { 438 serverctrlsU = controlarrayWtoU( serverctrls ); 439 if (!serverctrlsU) goto exit; 440 } 441 if (clientctrls) { 442 clientctrlsU = controlarrayWtoU( clientctrls ); 443 if (!clientctrlsU) goto exit; 444 } 445 446 credU.bv_len = cred->bv_len; 447 credU.bv_val = cred->bv_val; 448 449 ret = map_error( ldap_sasl_bind_s( ld, dnU, mechanismU, &credU, 450 serverctrlsU, clientctrlsU, (struct berval **)serverdata )); 451 452 exit: 453 strfreeU( dnU ); 454 strfreeU( mechanismU ); 455 controlarrayfreeU( serverctrlsU ); 456 controlarrayfreeU( clientctrlsU ); 457 458 #endif 459 return ret; 460 } 461 462 /*********************************************************************** 463 * ldap_simple_bindA (WLDAP32.@) 464 * 465 * See ldap_simple_bindW. 466 */ 467 ULONG CDECL ldap_simple_bindA( WLDAP32_LDAP *ld, PCHAR dn, PCHAR passwd ) 468 { 469 ULONG ret = WLDAP32_LDAP_NOT_SUPPORTED; 470 #ifdef HAVE_LDAP 471 WCHAR *dnW = NULL, *passwdW = NULL; 472 473 ret = WLDAP32_LDAP_NO_MEMORY; 474 475 TRACE( "(%p, %s, %p)\n", ld, debugstr_a(dn), passwd ); 476 477 if (!ld) return ~0u; 478 479 if (dn) { 480 dnW = strAtoW( dn ); 481 if (!dnW) goto exit; 482 } 483 if (passwd) { 484 passwdW = strAtoW( passwd ); 485 if (!passwdW) goto exit; 486 } 487 488 ret = ldap_simple_bindW( ld, dnW, passwdW ); 489 490 exit: 491 strfreeW( dnW ); 492 strfreeW( passwdW ); 493 494 #endif 495 return ret; 496 } 497 498 /*********************************************************************** 499 * ldap_simple_bindW (WLDAP32.@) 500 * 501 * Authenticate with an LDAP server (asynchronous operation). 502 * 503 * PARAMS 504 * ld [I] Pointer to an LDAP context. 505 * dn [I] DN of entry to bind as. 506 * passwd [I] Password string. 507 * 508 * RETURNS 509 * Success: Message ID of the bind operation. 510 * Failure: An LDAP error code. 511 * 512 * NOTES 513 * Set dn and passwd to NULL to bind as an anonymous user. 514 */ 515 ULONG CDECL ldap_simple_bindW( WLDAP32_LDAP *ld, PWCHAR dn, PWCHAR passwd ) 516 { 517 ULONG ret = WLDAP32_LDAP_NOT_SUPPORTED; 518 #ifdef HAVE_LDAP 519 char *dnU = NULL, *passwdU = NULL; 520 struct berval pwd = { 0, NULL }; 521 int msg; 522 523 ret = WLDAP32_LDAP_NO_MEMORY; 524 525 TRACE( "(%p, %s, %p)\n", ld, debugstr_w(dn), passwd ); 526 527 if (!ld) return ~0u; 528 529 if (dn) { 530 dnU = strWtoU( dn ); 531 if (!dnU) goto exit; 532 } 533 if (passwd) { 534 passwdU = strWtoU( passwd ); 535 if (!passwdU) goto exit; 536 537 pwd.bv_len = strlen( passwdU ); 538 pwd.bv_val = passwdU; 539 } 540 541 ret = ldap_sasl_bind( ld, dnU, LDAP_SASL_SIMPLE, &pwd, NULL, NULL, &msg ); 542 543 if (ret == LDAP_SUCCESS) 544 ret = msg; 545 else 546 ret = ~0u; 547 548 exit: 549 strfreeU( dnU ); 550 strfreeU( passwdU ); 551 552 #endif 553 return ret; 554 } 555 556 /*********************************************************************** 557 * ldap_simple_bind_sA (WLDAP32.@) 558 * 559 * See ldap_simple_bind_sW. 560 */ 561 ULONG CDECL ldap_simple_bind_sA( WLDAP32_LDAP *ld, PCHAR dn, PCHAR passwd ) 562 { 563 ULONG ret = WLDAP32_LDAP_NOT_SUPPORTED; 564 #ifdef HAVE_LDAP 565 WCHAR *dnW = NULL, *passwdW = NULL; 566 567 ret = WLDAP32_LDAP_NO_MEMORY; 568 569 TRACE( "(%p, %s, %p)\n", ld, debugstr_a(dn), passwd ); 570 571 if (!ld) return WLDAP32_LDAP_PARAM_ERROR; 572 573 if (dn) { 574 dnW = strAtoW( dn ); 575 if (!dnW) goto exit; 576 } 577 if (passwd) { 578 passwdW = strAtoW( passwd ); 579 if (!passwdW) goto exit; 580 } 581 582 ret = ldap_simple_bind_sW( ld, dnW, passwdW ); 583 584 exit: 585 strfreeW( dnW ); 586 strfreeW( passwdW ); 587 588 #endif 589 return ret; 590 } 591 592 /*********************************************************************** 593 * ldap_simple_bind_sW (WLDAP32.@) 594 * 595 * Authenticate with an LDAP server (synchronous operation). 596 * 597 * PARAMS 598 * ld [I] Pointer to an LDAP context. 599 * dn [I] DN of entry to bind as. 600 * passwd [I] Password string. 601 * 602 * RETURNS 603 * Success: LDAP_SUCCESS 604 * Failure: An LDAP error code. 605 * 606 * NOTES 607 * Set dn and passwd to NULL to bind as an anonymous user. 608 */ 609 ULONG CDECL ldap_simple_bind_sW( WLDAP32_LDAP *ld, PWCHAR dn, PWCHAR passwd ) 610 { 611 ULONG ret = WLDAP32_LDAP_NOT_SUPPORTED; 612 #ifdef HAVE_LDAP 613 char *dnU = NULL, *passwdU = NULL; 614 struct berval pwd = { 0, NULL }; 615 616 ret = WLDAP32_LDAP_NO_MEMORY; 617 618 TRACE( "(%p, %s, %p)\n", ld, debugstr_w(dn), passwd ); 619 620 if (!ld) return WLDAP32_LDAP_PARAM_ERROR; 621 622 if (dn) { 623 dnU = strWtoU( dn ); 624 if (!dnU) goto exit; 625 } 626 if (passwd) { 627 passwdU = strWtoU( passwd ); 628 if (!passwdU) goto exit; 629 630 pwd.bv_len = strlen( passwdU ); 631 pwd.bv_val = passwdU; 632 } 633 634 ret = map_error( ldap_sasl_bind_s( ld, dnU, LDAP_SASL_SIMPLE, &pwd, NULL, NULL, NULL )); 635 636 exit: 637 strfreeU( dnU ); 638 strfreeU( passwdU ); 639 640 #endif 641 return ret; 642 } 643 644 /*********************************************************************** 645 * ldap_unbind (WLDAP32.@) 646 * 647 * Close LDAP connection and free resources (asynchronous operation). 648 * 649 * PARAMS 650 * ld [I] Pointer to an LDAP context. 651 * 652 * RETURNS 653 * Success: LDAP_SUCCESS 654 * Failure: An LDAP error code. 655 */ 656 ULONG CDECL WLDAP32_ldap_unbind( WLDAP32_LDAP *ld ) 657 { 658 ULONG ret = WLDAP32_LDAP_NOT_SUPPORTED; 659 #ifdef HAVE_LDAP 660 661 TRACE( "(%p)\n", ld ); 662 663 if (ld) 664 ret = map_error( ldap_unbind_ext( ld, NULL, NULL )); 665 else 666 ret = WLDAP32_LDAP_PARAM_ERROR; 667 668 #endif 669 return ret; 670 } 671 672 /*********************************************************************** 673 * ldap_unbind_s (WLDAP32.@) 674 * 675 * Close LDAP connection and free resources (synchronous operation). 676 * 677 * PARAMS 678 * ld [I] Pointer to an LDAP context. 679 * 680 * RETURNS 681 * Success: LDAP_SUCCESS 682 * Failure: An LDAP error code. 683 */ 684 ULONG CDECL WLDAP32_ldap_unbind_s( WLDAP32_LDAP *ld ) 685 { 686 ULONG ret = WLDAP32_LDAP_NOT_SUPPORTED; 687 #ifdef HAVE_LDAP 688 689 TRACE( "(%p)\n", ld ); 690 691 if (ld) 692 ret = map_error( ldap_unbind_ext_s( ld, NULL, NULL )); 693 else 694 ret = WLDAP32_LDAP_PARAM_ERROR; 695 696 #endif 697 return ret; 698 } 699