Release 1.5.29.
[wine] / dlls / wldap32 / bind.c
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 }