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_searchA (WLDAP32.@) 41 * 42 * See ldap_searchW. 43 */ 44 ULONG CDECL ldap_searchA( WLDAP32_LDAP *ld, PCHAR base, ULONG scope, PCHAR filter, 45 PCHAR attrs[], ULONG attrsonly ) 46 { 47 ULONG ret = WLDAP32_LDAP_NOT_SUPPORTED; 48 #ifdef HAVE_LDAP 49 WCHAR *baseW = NULL, *filterW = NULL, **attrsW = NULL; 50 51 ret = WLDAP32_LDAP_NO_MEMORY; 52 53 TRACE( "(%p, %s, 0x%08x, %s, %p, 0x%08x)\n", ld, debugstr_a(base), 54 scope, debugstr_a(filter), attrs, attrsonly ); 55 56 if (!ld) return ~0u; 57 58 if (base) { 59 baseW = strAtoW( base ); 60 if (!baseW) goto exit; 61 } 62 if (filter) { 63 filterW = strAtoW( filter ); 64 if (!filterW) goto exit; 65 } 66 if (attrs) { 67 attrsW = strarrayAtoW( attrs ); 68 if (!attrsW) goto exit; 69 } 70 71 ret = ldap_searchW( ld, baseW, scope, filterW, attrsW, attrsonly ); 72 73 exit: 74 strfreeW( baseW ); 75 strfreeW( filterW ); 76 strarrayfreeW( attrsW ); 77 78 #endif 79 return ret; 80 } 81 82 /*********************************************************************** 83 * ldap_searchW (WLDAP32.@) 84 * 85 * Search a directory tree (asynchronous operation). 86 * 87 * PARAMS 88 * ld [I] Pointer to an LDAP context. 89 * base [I] Starting point for the search. 90 * scope [I] Search scope. One of LDAP_SCOPE_BASE, 91 * LDAP_SCOPE_ONELEVEL and LDAP_SCOPE_SUBTREE. 92 * filter [I] Search filter. 93 * attrs [I] Attributes to return. 94 * attrsonly [I] Return no values, only attributes. 95 * 96 * RETURNS 97 * Success: Message ID of the search operation. 98 * Failure: ~0u 99 * 100 * NOTES 101 * Call ldap_result with the message ID to get the result of 102 * the operation. Cancel the operation by calling ldap_abandon 103 * with the message ID. 104 */ 105 ULONG CDECL ldap_searchW( WLDAP32_LDAP *ld, PWCHAR base, ULONG scope, PWCHAR filter, 106 PWCHAR attrs[], ULONG attrsonly ) 107 { 108 ULONG ret = WLDAP32_LDAP_NOT_SUPPORTED; 109 #ifdef HAVE_LDAP 110 char *baseU = NULL, *filterU = NULL, **attrsU = NULL; 111 int msg; 112 113 ret = WLDAP32_LDAP_NO_MEMORY; 114 115 TRACE( "(%p, %s, 0x%08x, %s, %p, 0x%08x)\n", ld, debugstr_w(base), 116 scope, debugstr_w(filter), attrs, attrsonly ); 117 118 if (!ld) return ~0u; 119 120 if (base) { 121 baseU = strWtoU( base ); 122 if (!baseU) goto exit; 123 } 124 if (filter) { 125 filterU = strWtoU( filter ); 126 if (!filterU) goto exit; 127 } 128 if (attrs) { 129 attrsU = strarrayWtoU( attrs ); 130 if (!attrsU) goto exit; 131 } 132 133 ret = ldap_search_ext( ld, baseU, scope, filterU, attrsU, attrsonly, 134 NULL, NULL, NULL, 0, &msg ); 135 136 if (ret == LDAP_SUCCESS) 137 ret = msg; 138 else 139 ret = ~0u; 140 141 exit: 142 strfreeU( baseU ); 143 strfreeU( filterU ); 144 strarrayfreeU( attrsU ); 145 146 #endif 147 return ret; 148 } 149 150 /*********************************************************************** 151 * ldap_search_extA (WLDAP32.@) 152 * 153 * See ldap_search_extW. 154 */ 155 ULONG CDECL ldap_search_extA( WLDAP32_LDAP *ld, PCHAR base, ULONG scope, 156 PCHAR filter, PCHAR attrs[], ULONG attrsonly, PLDAPControlA *serverctrls, 157 PLDAPControlA *clientctrls, ULONG timelimit, ULONG sizelimit, ULONG *message ) 158 { 159 ULONG ret = WLDAP32_LDAP_NOT_SUPPORTED; 160 #ifdef HAVE_LDAP 161 WCHAR *baseW = NULL, *filterW = NULL, **attrsW = NULL; 162 LDAPControlW **serverctrlsW = NULL, **clientctrlsW = NULL; 163 164 ret = WLDAP32_LDAP_NO_MEMORY; 165 166 TRACE( "(%p, %s, 0x%08x, %s, %p, 0x%08x, %p, %p, 0x%08x, 0x%08x, %p)\n", 167 ld, debugstr_a(base), scope, debugstr_a(filter), attrs, attrsonly, 168 serverctrls, clientctrls, timelimit, sizelimit, message ); 169 170 if (!ld) return WLDAP32_LDAP_PARAM_ERROR; 171 172 if (base) { 173 baseW = strAtoW( base ); 174 if (!baseW) goto exit; 175 } 176 if (filter) 177 { 178 filterW = strAtoW( filter ); 179 if (!filterW) goto exit; 180 } 181 if (attrs) { 182 attrsW = strarrayAtoW( attrs ); 183 if (!attrsW) goto exit; 184 } 185 if (serverctrls) { 186 serverctrlsW = controlarrayAtoW( serverctrls ); 187 if (!serverctrlsW) goto exit; 188 } 189 if (clientctrls) { 190 clientctrlsW = controlarrayAtoW( clientctrls ); 191 if (!clientctrlsW) goto exit; 192 } 193 194 ret = ldap_search_extW( ld, baseW, scope, filterW, attrsW, attrsonly, 195 serverctrlsW, clientctrlsW, timelimit, sizelimit, message ); 196 197 exit: 198 strfreeW( baseW ); 199 strfreeW( filterW ); 200 strarrayfreeW( attrsW ); 201 controlarrayfreeW( serverctrlsW ); 202 controlarrayfreeW( clientctrlsW ); 203 204 #endif 205 return ret; 206 } 207 208 /*********************************************************************** 209 * ldap_search_extW (WLDAP32.@) 210 * 211 * Search a directory tree (asynchronous operation). 212 * 213 * PARAMS 214 * ld [I] Pointer to an LDAP context. 215 * base [I] Starting point for the search. 216 * scope [I] Search scope. One of LDAP_SCOPE_BASE, 217 * LDAP_SCOPE_ONELEVEL and LDAP_SCOPE_SUBTREE. 218 * filter [I] Search filter. 219 * attrs [I] Attributes to return. 220 * attrsonly [I] Return no values, only attributes. 221 * serverctrls [I] Array of LDAP server controls. 222 * clientctrls [I] Array of LDAP client controls. 223 * timelimit [I] Timeout in seconds. 224 * sizelimit [I] Maximum number of entries to return. Zero means unlimited. 225 * message [O] Message ID of the search operation. 226 * 227 * RETURNS 228 * Success: LDAP_SUCCESS 229 * Failure: An LDAP error code. 230 * 231 * NOTES 232 * Call ldap_result with the message ID to get the result of 233 * the operation. Cancel the operation by calling ldap_abandon 234 * with the message ID. 235 */ 236 ULONG CDECL ldap_search_extW( WLDAP32_LDAP *ld, PWCHAR base, ULONG scope, 237 PWCHAR filter, PWCHAR attrs[], ULONG attrsonly, PLDAPControlW *serverctrls, 238 PLDAPControlW *clientctrls, ULONG timelimit, ULONG sizelimit, ULONG *message ) 239 { 240 ULONG ret = WLDAP32_LDAP_NOT_SUPPORTED; 241 #ifdef HAVE_LDAP 242 char *baseU = NULL, *filterU = NULL, **attrsU = NULL; 243 LDAPControl **serverctrlsU = NULL, **clientctrlsU = NULL; 244 struct timeval tv, *tvp = NULL; 245 246 ret = WLDAP32_LDAP_NO_MEMORY; 247 248 TRACE( "(%p, %s, 0x%08x, %s, %p, 0x%08x, %p, %p, 0x%08x, 0x%08x, %p)\n", 249 ld, debugstr_w(base), scope, debugstr_w(filter), attrs, attrsonly, 250 serverctrls, clientctrls, timelimit, sizelimit, message ); 251 252 if (!ld) return ~0u; 253 254 if (base) { 255 baseU = strWtoU( base ); 256 if (!baseU) goto exit; 257 } 258 if (filter) { 259 filterU = strWtoU( filter ); 260 if (!filterU) goto exit; 261 } 262 if (attrs) { 263 attrsU = strarrayWtoU( attrs ); 264 if (!attrsU) goto exit; 265 } 266 if (serverctrls) { 267 serverctrlsU = controlarrayWtoU( serverctrls ); 268 if (!serverctrlsU) goto exit; 269 } 270 if (clientctrls) { 271 clientctrlsU = controlarrayWtoU( clientctrls ); 272 if (!clientctrlsU) goto exit; 273 } 274 275 if (timelimit) 276 { 277 tv.tv_sec = timelimit; 278 tv.tv_usec = 0; 279 tvp = &tv; 280 } 281 282 ret = map_error( ldap_search_ext( ld, baseU, scope, filterU, attrsU, attrsonly, 283 serverctrlsU, clientctrlsU, tvp, sizelimit, (int *)message )); 284 285 exit: 286 strfreeU( baseU ); 287 strfreeU( filterU ); 288 strarrayfreeU( attrsU ); 289 controlarrayfreeU( serverctrlsU ); 290 controlarrayfreeU( clientctrlsU ); 291 292 #endif 293 return ret; 294 } 295 296 /*********************************************************************** 297 * ldap_search_ext_sA (WLDAP32.@) 298 * 299 * See ldap_search_ext_sW. 300 */ 301 ULONG CDECL ldap_search_ext_sA( WLDAP32_LDAP *ld, PCHAR base, ULONG scope, 302 PCHAR filter, PCHAR attrs[], ULONG attrsonly, PLDAPControlA *serverctrls, 303 PLDAPControlA *clientctrls, struct l_timeval* timeout, ULONG sizelimit, WLDAP32_LDAPMessage **res ) 304 { 305 ULONG ret = WLDAP32_LDAP_NOT_SUPPORTED; 306 #ifdef HAVE_LDAP 307 WCHAR *baseW = NULL, *filterW = NULL, **attrsW = NULL; 308 LDAPControlW **serverctrlsW = NULL, **clientctrlsW = NULL; 309 310 ret = WLDAP32_LDAP_NO_MEMORY; 311 312 TRACE( "(%p, %s, 0x%08x, %s, %p, 0x%08x, %p, %p, %p, 0x%08x, %p)\n", 313 ld, debugstr_a(base), scope, debugstr_a(filter), attrs, attrsonly, 314 serverctrls, clientctrls, timeout, sizelimit, res ); 315 316 if (!ld || !res) return WLDAP32_LDAP_PARAM_ERROR; 317 318 if (base) { 319 baseW = strAtoW( base ); 320 if (!baseW) goto exit; 321 } 322 if (filter) { 323 filterW = strAtoW( filter ); 324 if (!filterW) goto exit; 325 } 326 if (attrs) { 327 attrsW = strarrayAtoW( attrs ); 328 if (!attrsW) goto exit; 329 } 330 if (serverctrls) { 331 serverctrlsW = controlarrayAtoW( serverctrls ); 332 if (!serverctrlsW) goto exit; 333 } 334 if (clientctrls) { 335 clientctrlsW = controlarrayAtoW( clientctrls ); 336 if (!clientctrlsW) goto exit; 337 } 338 339 ret = ldap_search_ext_sW( ld, baseW, scope, filterW, attrsW, attrsonly, 340 serverctrlsW, clientctrlsW, timeout, sizelimit, res ); 341 342 exit: 343 strfreeW( baseW ); 344 strfreeW( filterW ); 345 strarrayfreeW( attrsW ); 346 controlarrayfreeW( serverctrlsW ); 347 controlarrayfreeW( clientctrlsW ); 348 349 #endif 350 return ret; 351 } 352 353 /*********************************************************************** 354 * ldap_search_ext_sW (WLDAP32.@) 355 * 356 * Search a directory tree (synchronous operation). 357 * 358 * PARAMS 359 * ld [I] Pointer to an LDAP context. 360 * base [I] Starting point for the search. 361 * scope [I] Search scope. One of LDAP_SCOPE_BASE, 362 * LDAP_SCOPE_ONELEVEL and LDAP_SCOPE_SUBTREE. 363 * filter [I] Search filter. 364 * attrs [I] Attributes to return. 365 * attrsonly [I] Return no values, only attributes. 366 * serverctrls [I] Array of LDAP server controls. 367 * clientctrls [I] Array of LDAP client controls. 368 * timeout [I] Timeout in seconds. 369 * sizelimit [I] Maximum number of entries to return. Zero means unlimited. 370 * res [O] Results of the search operation. 371 * 372 * RETURNS 373 * Success: LDAP_SUCCESS 374 * Failure: An LDAP error code. 375 * 376 * NOTES 377 * Call ldap_msgfree to free the results. 378 */ 379 ULONG CDECL ldap_search_ext_sW( WLDAP32_LDAP *ld, PWCHAR base, ULONG scope, 380 PWCHAR filter, PWCHAR attrs[], ULONG attrsonly, PLDAPControlW *serverctrls, 381 PLDAPControlW *clientctrls, struct l_timeval* timeout, ULONG sizelimit, WLDAP32_LDAPMessage **res ) 382 { 383 ULONG ret = WLDAP32_LDAP_NOT_SUPPORTED; 384 #ifdef HAVE_LDAP 385 char *baseU = NULL, *filterU = NULL, **attrsU = NULL; 386 LDAPControl **serverctrlsU = NULL, **clientctrlsU = NULL; 387 388 ret = WLDAP32_LDAP_NO_MEMORY; 389 390 TRACE( "(%p, %s, 0x%08x, %s, %p, 0x%08x, %p, %p, %p, 0x%08x, %p)\n", 391 ld, debugstr_w(base), scope, debugstr_w(filter), attrs, attrsonly, 392 serverctrls, clientctrls, timeout, sizelimit, res ); 393 394 if (!ld || !res) return WLDAP32_LDAP_PARAM_ERROR; 395 396 if (base) { 397 baseU = strWtoU( base ); 398 if (!baseU) goto exit; 399 } 400 if (filter) { 401 filterU = strWtoU( filter ); 402 if (!filterU) goto exit; 403 } 404 if (attrs) { 405 attrsU = strarrayWtoU( attrs ); 406 if (!attrsU) goto exit; 407 } 408 if (serverctrls) { 409 serverctrlsU = controlarrayWtoU( serverctrls ); 410 if (!serverctrlsU) goto exit; 411 } 412 if (clientctrls) { 413 clientctrlsU = controlarrayWtoU( clientctrls ); 414 if (!clientctrlsU) goto exit; 415 } 416 417 ret = map_error( ldap_search_ext_s( ld, baseU, scope, filterU, attrsU, attrsonly, 418 serverctrlsU, clientctrlsU, (struct timeval *)timeout, 419 sizelimit, res )); 420 421 exit: 422 strfreeU( baseU ); 423 strfreeU( filterU ); 424 strarrayfreeU( attrsU ); 425 controlarrayfreeU( serverctrlsU ); 426 controlarrayfreeU( clientctrlsU ); 427 428 #endif 429 return ret; 430 } 431 432 /*********************************************************************** 433 * ldap_search_sA (WLDAP32.@) 434 * 435 * See ldap_search_sW. 436 */ 437 ULONG CDECL ldap_search_sA( WLDAP32_LDAP *ld, PCHAR base, ULONG scope, PCHAR filter, 438 PCHAR attrs[], ULONG attrsonly, WLDAP32_LDAPMessage **res ) 439 { 440 ULONG ret = WLDAP32_LDAP_NOT_SUPPORTED; 441 #ifdef HAVE_LDAP 442 WCHAR *baseW = NULL, *filterW = NULL, **attrsW = NULL; 443 444 ret = WLDAP32_LDAP_NO_MEMORY; 445 446 TRACE( "(%p, %s, 0x%08x, %s, %p, 0x%08x, %p)\n", ld, debugstr_a(base), 447 scope, debugstr_a(filter), attrs, attrsonly, res ); 448 449 if (!ld || !res) return WLDAP32_LDAP_PARAM_ERROR; 450 451 if (base) { 452 baseW = strAtoW( base ); 453 if (!baseW) goto exit; 454 } 455 if (filter) { 456 filterW = strAtoW( filter ); 457 if (!filterW) goto exit; 458 } 459 if (attrs) { 460 attrsW = strarrayAtoW( attrs ); 461 if (!attrsW) goto exit; 462 } 463 464 ret = ldap_search_sW( ld, baseW, scope, filterW, attrsW, attrsonly, res ); 465 466 exit: 467 strfreeW( baseW ); 468 strfreeW( filterW ); 469 strarrayfreeW( attrsW ); 470 471 #endif 472 return ret; 473 } 474 475 /*********************************************************************** 476 * ldap_search_sW (WLDAP32.@) 477 * 478 * Search a directory tree (synchronous operation). 479 * 480 * PARAMS 481 * ld [I] Pointer to an LDAP context. 482 * base [I] Starting point for the search. 483 * scope [I] Search scope. One of LDAP_SCOPE_BASE, 484 * LDAP_SCOPE_ONELEVEL and LDAP_SCOPE_SUBTREE. 485 * filter [I] Search filter. 486 * attrs [I] Attributes to return. 487 * attrsonly [I] Return no values, only attributes. 488 * res [O] Results of the search operation. 489 * 490 * RETURNS 491 * Success: LDAP_SUCCESS 492 * Failure: An LDAP error code. 493 * 494 * NOTES 495 * Call ldap_msgfree to free the results. 496 */ 497 ULONG CDECL ldap_search_sW( WLDAP32_LDAP *ld, PWCHAR base, ULONG scope, PWCHAR filter, 498 PWCHAR attrs[], ULONG attrsonly, WLDAP32_LDAPMessage **res ) 499 { 500 ULONG ret = WLDAP32_LDAP_NOT_SUPPORTED; 501 #ifdef HAVE_LDAP 502 char *baseU = NULL, *filterU = NULL, **attrsU = NULL; 503 504 ret = WLDAP32_LDAP_NO_MEMORY; 505 506 TRACE( "(%p, %s, 0x%08x, %s, %p, 0x%08x, %p)\n", ld, debugstr_w(base), 507 scope, debugstr_w(filter), attrs, attrsonly, res ); 508 509 if (!ld || !res) return WLDAP32_LDAP_PARAM_ERROR; 510 511 if (base) { 512 baseU = strWtoU( base ); 513 if (!baseU) goto exit; 514 } 515 if (filter) { 516 filterU = strWtoU( filter ); 517 if (!filterU) goto exit; 518 } 519 if (attrs) { 520 attrsU = strarrayWtoU( attrs ); 521 if (!attrsU) goto exit; 522 } 523 524 ret = map_error( ldap_search_ext_s( ld, baseU, scope, filterU, attrsU, attrsonly, 525 NULL, NULL, NULL, 0, res )); 526 527 exit: 528 strfreeU( baseU ); 529 strfreeU( filterU ); 530 strarrayfreeU( attrsU ); 531 532 #endif 533 return ret; 534 } 535 536 /*********************************************************************** 537 * ldap_search_stA (WLDAP32.@) 538 * 539 * See ldap_search_stW. 540 */ 541 ULONG CDECL ldap_search_stA( WLDAP32_LDAP *ld, const PCHAR base, ULONG scope, 542 const PCHAR filter, PCHAR attrs[], ULONG attrsonly, 543 struct l_timeval *timeout, WLDAP32_LDAPMessage **res ) 544 { 545 ULONG ret = WLDAP32_LDAP_NOT_SUPPORTED; 546 #ifdef HAVE_LDAP 547 WCHAR *baseW = NULL, *filterW = NULL, **attrsW = NULL; 548 549 ret = WLDAP32_LDAP_NO_MEMORY; 550 551 TRACE( "(%p, %s, 0x%08x, %s, %p, 0x%08x, %p, %p)\n", ld, 552 debugstr_a(base), scope, debugstr_a(filter), attrs, 553 attrsonly, timeout, res ); 554 555 if (!ld || !res) return WLDAP32_LDAP_PARAM_ERROR; 556 557 if (base) { 558 baseW = strAtoW( base ); 559 if (!baseW) goto exit; 560 } 561 if (filter) { 562 filterW = strAtoW( filter ); 563 if (!filterW) goto exit; 564 } 565 if (attrs) { 566 attrsW = strarrayAtoW( attrs ); 567 if (!attrsW) goto exit; 568 } 569 570 ret = ldap_search_stW( ld, baseW, scope, filterW, attrsW, attrsonly, 571 timeout, res ); 572 573 exit: 574 strfreeW( baseW ); 575 strfreeW( filterW ); 576 strarrayfreeW( attrsW ); 577 578 #endif 579 return ret; 580 } 581 582 /*********************************************************************** 583 * ldap_search_stW (WLDAP32.@) 584 * 585 * Search a directory tree (synchronous operation). 586 * 587 * PARAMS 588 * ld [I] Pointer to an LDAP context. 589 * base [I] Starting point for the search. 590 * scope [I] Search scope. One of LDAP_SCOPE_BASE, 591 * LDAP_SCOPE_ONELEVEL and LDAP_SCOPE_SUBTREE. 592 * filter [I] Search filter. 593 * attrs [I] Attributes to return. 594 * attrsonly [I] Return no values, only attributes. 595 * timeout [I] Timeout in seconds. 596 * res [O] Results of the search operation. 597 * 598 * RETURNS 599 * Success: LDAP_SUCCESS 600 * Failure: An LDAP error code. 601 * 602 * NOTES 603 * Call ldap_msgfree to free the results. 604 */ 605 ULONG CDECL ldap_search_stW( WLDAP32_LDAP *ld, const PWCHAR base, ULONG scope, 606 const PWCHAR filter, PWCHAR attrs[], ULONG attrsonly, 607 struct l_timeval *timeout, WLDAP32_LDAPMessage **res ) 608 { 609 ULONG ret = WLDAP32_LDAP_NOT_SUPPORTED; 610 #ifdef HAVE_LDAP 611 char *baseU = NULL, *filterU = NULL, **attrsU = NULL; 612 613 ret = WLDAP32_LDAP_NO_MEMORY; 614 615 TRACE( "(%p, %s, 0x%08x, %s, %p, 0x%08x, %p, %p)\n", ld, 616 debugstr_w(base), scope, debugstr_w(filter), attrs, 617 attrsonly, timeout, res ); 618 619 if (!ld || !res) return WLDAP32_LDAP_PARAM_ERROR; 620 621 if (base) { 622 baseU = strWtoU( base ); 623 if (!baseU) goto exit; 624 } 625 if (filter) { 626 filterU = strWtoU( filter ); 627 if (!filterU) goto exit; 628 } 629 if (attrs) { 630 attrsU = strarrayWtoU( attrs ); 631 if (!attrsU) goto exit; 632 } 633 634 ret = map_error( ldap_search_ext_s( ld, baseU, scope, filterU, attrsU, attrsonly, 635 NULL, NULL, (struct timeval *)timeout, 0, res )); 636 637 exit: 638 strfreeU( baseU ); 639 strfreeU( filterU ); 640 strarrayfreeU( attrsU ); 641 642 #endif 643 return ret; 644 } 645