Add parameter "queue" to wb_int_trans_send
[samba.git] / source3 / lib / wbclient.c
1 /*
2    Unix SMB/CIFS implementation.
3    Infrastructure for async winbind requests
4    Copyright (C) Volker Lendecke 2008
5
6    This program is free software; you can redistribute it and/or modify
7    it under the terms of the GNU General Public License as published by
8    the Free Software Foundation; either version 3 of the License, or
9    (at your option) any later version.
10
11    This program 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
14    GNU General Public License for more details.
15
16    You should have received a copy of the GNU General Public License
17    along with this program.  If not, see <http://www.gnu.org/licenses/>.
18 */
19
20 #include "includes.h"
21 #include "wbc_async.h"
22
23 static int make_nonstd_fd(int fd)
24 {
25         int i;
26         int sys_errno = 0;
27         int fds[3];
28         int num_fds = 0;
29
30         if (fd == -1) {
31                 return -1;
32         }
33         while (fd < 3) {
34                 fds[num_fds++] = fd;
35                 fd = dup(fd);
36                 if (fd == -1) {
37                         sys_errno = errno;
38                         break;
39                 }
40         }
41         for (i=0; i<num_fds; i++) {
42                 close(fds[i]);
43         }
44         if (fd == -1) {
45                 errno = sys_errno;
46         }
47         return fd;
48 }
49
50 /****************************************************************************
51  Set a fd into blocking/nonblocking mode. Uses POSIX O_NONBLOCK if available,
52  else
53  if SYSV use O_NDELAY
54  if BSD use FNDELAY
55  Set close on exec also.
56 ****************************************************************************/
57
58 static int make_safe_fd(int fd)
59 {
60         int result, flags;
61         int new_fd = make_nonstd_fd(fd);
62
63         if (new_fd == -1) {
64                 goto fail;
65         }
66
67         /* Socket should be nonblocking. */
68
69 #ifdef O_NONBLOCK
70 #define FLAG_TO_SET O_NONBLOCK
71 #else
72 #ifdef SYSV
73 #define FLAG_TO_SET O_NDELAY
74 #else /* BSD */
75 #define FLAG_TO_SET FNDELAY
76 #endif
77 #endif
78
79         if ((flags = fcntl(new_fd, F_GETFL)) == -1) {
80                 goto fail;
81         }
82
83         flags |= FLAG_TO_SET;
84         if (fcntl(new_fd, F_SETFL, flags) == -1) {
85                 goto fail;
86         }
87
88 #undef FLAG_TO_SET
89
90         /* Socket should be closed on exec() */
91 #ifdef FD_CLOEXEC
92         result = flags = fcntl(new_fd, F_GETFD, 0);
93         if (flags >= 0) {
94                 flags |= FD_CLOEXEC;
95                 result = fcntl( new_fd, F_SETFD, flags );
96         }
97         if (result < 0) {
98                 goto fail;
99         }
100 #endif
101         return new_fd;
102
103  fail:
104         if (new_fd != -1) {
105                 int sys_errno = errno;
106                 close(new_fd);
107                 errno = sys_errno;
108         }
109         return -1;
110 }
111
112 static bool winbind_closed_fd(int fd)
113 {
114         struct timeval tv;
115         fd_set r_fds;
116
117         if (fd == -1) {
118                 return true;
119         }
120
121         FD_ZERO(&r_fds);
122         FD_SET(fd, &r_fds);
123         ZERO_STRUCT(tv);
124
125         if ((select(fd+1, &r_fds, NULL, NULL, &tv) == -1)
126             || FD_ISSET(fd, &r_fds)) {
127                 return true;
128         }
129
130         return false;
131 }
132
133 struct wb_context *wb_context_init(TALLOC_CTX *mem_ctx)
134 {
135         struct wb_context *result;
136
137         result = talloc(mem_ctx, struct wb_context);
138         if (result == NULL) {
139                 return NULL;
140         }
141         result->queue = async_req_queue_init(result);
142         if (result->queue == NULL) {
143                 TALLOC_FREE(result);
144                 return NULL;
145         }
146         result->fd = -1;
147         return result;
148 }
149
150 struct wb_connect_state {
151         int dummy;
152 };
153
154 static void wbc_connect_connected(struct tevent_req *subreq);
155
156 static struct async_req *wb_connect_send(TALLOC_CTX *mem_ctx,
157                                           struct tevent_context *ev,
158                                           struct wb_context *wb_ctx,
159                                           const char *dir)
160 {
161         struct async_req *result;
162         struct tevent_req *subreq;
163         struct wb_connect_state *state;
164         struct sockaddr_un sunaddr;
165         struct stat st;
166         char *path = NULL;
167         wbcErr wbc_err;
168
169         if (!async_req_setup(mem_ctx, &result, &state,
170                              struct wb_connect_state)) {
171                 return NULL;
172         }
173
174         if (wb_ctx->fd != -1) {
175                 close(wb_ctx->fd);
176                 wb_ctx->fd = -1;
177         }
178
179         /* Check permissions on unix socket directory */
180
181         if (lstat(dir, &st) == -1) {
182                 wbc_err = WBC_ERR_WINBIND_NOT_AVAILABLE;
183                 goto post_status;
184         }
185
186         if (!S_ISDIR(st.st_mode) ||
187             (st.st_uid != 0 && st.st_uid != geteuid())) {
188                 wbc_err = WBC_ERR_WINBIND_NOT_AVAILABLE;
189                 goto post_status;
190         }
191
192         /* Connect to socket */
193
194         path = talloc_asprintf(talloc_tos(), "%s/%s", dir,
195                                WINBINDD_SOCKET_NAME);
196         if (path == NULL) {
197                 goto nomem;
198         }
199
200         sunaddr.sun_family = AF_UNIX;
201         strlcpy(sunaddr.sun_path, path, sizeof(sunaddr.sun_path));
202         TALLOC_FREE(path);
203
204         /* If socket file doesn't exist, don't bother trying to connect
205            with retry.  This is an attempt to make the system usable when
206            the winbindd daemon is not running. */
207
208         if ((lstat(sunaddr.sun_path, &st) == -1)
209             || !S_ISSOCK(st.st_mode)
210             || (st.st_uid != 0 && st.st_uid != geteuid())) {
211                 wbc_err = WBC_ERR_WINBIND_NOT_AVAILABLE;
212                 goto post_status;
213         }
214
215         wb_ctx->fd = make_safe_fd(socket(AF_UNIX, SOCK_STREAM, 0));
216         if (wb_ctx->fd == -1) {
217                 wbc_err = map_wbc_err_from_errno(errno);
218                 goto post_status;
219         }
220
221         subreq = async_connect_send(mem_ctx, ev, wb_ctx->fd,
222                                     (struct sockaddr *)&sunaddr,
223                                     sizeof(sunaddr));
224         if (subreq == NULL) {
225                 goto nomem;
226         }
227         tevent_req_set_callback(subreq, wbc_connect_connected, result);
228
229         if (!tevent_req_set_endtime(subreq, ev, timeval_current_ofs(30, 0))) {
230                 goto nomem;
231         }
232
233         return result;
234
235  nomem:
236         wbc_err = WBC_ERR_NO_MEMORY;
237  post_status:
238         if (async_post_error(result, ev, wbc_err)) {
239                 return result;
240         }
241         TALLOC_FREE(result);
242         return NULL;
243 }
244
245 static void wbc_connect_connected(struct tevent_req *subreq)
246 {
247         struct async_req *req =
248                 tevent_req_callback_data(subreq, struct async_req);
249         int res, err;
250
251         res = async_connect_recv(subreq, &err);
252         TALLOC_FREE(subreq);
253         if (res == -1) {
254                 async_req_error(req, map_wbc_err_from_errno(err));
255                 return;
256         }
257         async_req_done(req);
258 }
259
260 static wbcErr wb_connect_recv(struct async_req *req)
261 {
262         return async_req_simple_recv_wbcerr(req);
263 }
264
265 static struct winbindd_request *winbindd_request_copy(
266         TALLOC_CTX *mem_ctx,
267         const struct winbindd_request *req)
268 {
269         struct winbindd_request *result;
270
271         result = (struct winbindd_request *)TALLOC_MEMDUP(
272                 mem_ctx, req, sizeof(struct winbindd_request));
273         if (result == NULL) {
274                 return NULL;
275         }
276
277         if (result->extra_len == 0) {
278                 return result;
279         }
280
281         result->extra_data.data = (char *)TALLOC_MEMDUP(
282                 result, result->extra_data.data, result->extra_len);
283         if (result->extra_data.data == NULL) {
284                 TALLOC_FREE(result);
285                 return NULL;
286         }
287         return result;
288 }
289
290 struct wb_int_trans_state {
291         struct tevent_context *ev;
292         int fd;
293         struct winbindd_request *wb_req;
294         struct winbindd_response *wb_resp;
295 };
296
297 static void wb_int_trans_write_done(struct tevent_req *subreq);
298 static void wb_int_trans_read_done(struct tevent_req *subreq);
299
300 static struct async_req *wb_int_trans_send(TALLOC_CTX *mem_ctx,
301                                            struct tevent_context *ev,
302                                            struct tevent_queue *queue, int fd,
303                                            struct winbindd_request *wb_req)
304 {
305         struct async_req *result;
306         struct tevent_req *subreq;
307         struct wb_int_trans_state *state;
308
309         if (!async_req_setup(mem_ctx, &result, &state,
310                              struct wb_int_trans_state)) {
311                 return NULL;
312         }
313
314         if (winbind_closed_fd(fd)) {
315                 if (!async_post_error(result, ev,
316                                       WBC_ERR_WINBIND_NOT_AVAILABLE)) {
317                         goto fail;
318                 }
319                 return result;
320         }
321
322         state->ev = ev;
323         state->fd = fd;
324         state->wb_req = wb_req;
325
326         state->wb_req->length = sizeof(struct winbindd_request);
327         state->wb_req->pid = getpid();
328
329         subreq = wb_req_write_send(state, state->ev, queue, state->fd,
330                                    state->wb_req);
331         if (subreq == NULL) {
332                 goto fail;
333         }
334         tevent_req_set_callback(subreq, wb_int_trans_write_done, result);
335
336         return result;
337
338  fail:
339         TALLOC_FREE(result);
340         return NULL;
341 }
342
343 static void wb_int_trans_write_done(struct tevent_req *subreq)
344 {
345         struct async_req *req = tevent_req_callback_data(
346                 subreq, struct async_req);
347         struct wb_int_trans_state *state = talloc_get_type_abort(
348                 req->private_data, struct wb_int_trans_state);
349         wbcErr wbc_err;
350
351         wbc_err = wb_req_write_recv(subreq);
352         TALLOC_FREE(subreq);
353         if (!WBC_ERROR_IS_OK(wbc_err)) {
354                 async_req_error(req, wbc_err);
355                 return;
356         }
357
358         subreq = wb_resp_read_send(state, state->ev, state->fd);
359         if (async_req_nomem(subreq, req)) {
360                 return;
361         }
362         tevent_req_set_callback(subreq, wb_int_trans_read_done, req);
363 }
364
365 static void wb_int_trans_read_done(struct tevent_req *subreq)
366 {
367         struct async_req *req = tevent_req_callback_data(
368                 subreq, struct async_req);
369         struct wb_int_trans_state *state = talloc_get_type_abort(
370                 req->private_data, struct wb_int_trans_state);
371         wbcErr wbc_err;
372
373         wbc_err = wb_resp_read_recv(subreq, state, &state->wb_resp);
374         TALLOC_FREE(subreq);
375         if (!WBC_ERROR_IS_OK(wbc_err)) {
376                 async_req_error(req, wbc_err);
377                 return;
378         }
379
380         async_req_done(req);
381 }
382
383 static wbcErr wb_int_trans_recv(struct async_req *req,
384                                 TALLOC_CTX *mem_ctx,
385                                 struct winbindd_response **presponse)
386 {
387         struct wb_int_trans_state *state = talloc_get_type_abort(
388                 req->private_data, struct wb_int_trans_state);
389         wbcErr wbc_err;
390
391         if (async_req_is_wbcerr(req, &wbc_err)) {
392                 return wbc_err;
393         }
394
395         *presponse = talloc_move(mem_ctx, &state->wb_resp);
396         return WBC_ERR_SUCCESS;
397 }
398
399 static const char *winbindd_socket_dir(void)
400 {
401 #ifdef SOCKET_WRAPPER
402         const char *env_dir;
403
404         env_dir = getenv(WINBINDD_SOCKET_DIR_ENVVAR);
405         if (env_dir) {
406                 return env_dir;
407         }
408 #endif
409
410         return WINBINDD_SOCKET_DIR;
411 }
412
413 struct wb_open_pipe_state {
414         struct wb_context *wb_ctx;
415         struct tevent_context *ev;
416         bool need_priv;
417         struct winbindd_request wb_req;
418 };
419
420 static void wb_open_pipe_connect_nonpriv_done(struct async_req *subreq);
421 static void wb_open_pipe_ping_done(struct async_req *subreq);
422 static void wb_open_pipe_getpriv_done(struct async_req *subreq);
423 static void wb_open_pipe_connect_priv_done(struct async_req *subreq);
424
425 static struct async_req *wb_open_pipe_send(TALLOC_CTX *mem_ctx,
426                                            struct tevent_context *ev,
427                                            struct wb_context *wb_ctx,
428                                            bool need_priv)
429 {
430         struct async_req *result;
431         struct async_req *subreq;
432         struct wb_open_pipe_state *state;
433
434         if (!async_req_setup(mem_ctx, &result, &state,
435                              struct wb_open_pipe_state)) {
436                 return NULL;
437         }
438         state->wb_ctx = wb_ctx;
439         state->ev = ev;
440         state->need_priv = need_priv;
441
442         if (wb_ctx->fd != -1) {
443                 close(wb_ctx->fd);
444                 wb_ctx->fd = -1;
445         }
446
447         subreq = wb_connect_send(state, ev, wb_ctx, winbindd_socket_dir());
448         if (subreq == NULL) {
449                 goto fail;
450         }
451
452         subreq->async.fn = wb_open_pipe_connect_nonpriv_done;
453         subreq->async.priv = result;
454         return result;
455
456  fail:
457         TALLOC_FREE(result);
458         return NULL;
459 }
460
461 static void wb_open_pipe_connect_nonpriv_done(struct async_req *subreq)
462 {
463         struct async_req *req = talloc_get_type_abort(
464                 subreq->async.priv, struct async_req);
465         struct wb_open_pipe_state *state = talloc_get_type_abort(
466                 req->private_data, struct wb_open_pipe_state);
467         wbcErr wbc_err;
468
469         wbc_err = wb_connect_recv(subreq);
470         TALLOC_FREE(subreq);
471         if (!WBC_ERROR_IS_OK(wbc_err)) {
472                 state->wb_ctx->is_priv = true;
473                 async_req_error(req, wbc_err);
474                 return;
475         }
476
477         ZERO_STRUCT(state->wb_req);
478         state->wb_req.cmd = WINBINDD_INTERFACE_VERSION;
479
480         subreq = wb_int_trans_send(state, state->ev, NULL, state->wb_ctx->fd,
481                                    &state->wb_req);
482         if (async_req_nomem(subreq, req)) {
483                 return;
484         }
485
486         subreq->async.fn = wb_open_pipe_ping_done;
487         subreq->async.priv = req;
488 }
489
490 static void wb_open_pipe_ping_done(struct async_req *subreq)
491 {
492         struct async_req *req = talloc_get_type_abort(
493                 subreq->async.priv, struct async_req);
494         struct wb_open_pipe_state *state = talloc_get_type_abort(
495                 req->private_data, struct wb_open_pipe_state);
496         struct winbindd_response *wb_resp;
497         wbcErr wbc_err;
498
499         wbc_err = wb_int_trans_recv(subreq, state, &wb_resp);
500         TALLOC_FREE(subreq);
501         if (!WBC_ERROR_IS_OK(wbc_err)) {
502                 async_req_error(req, wbc_err);
503                 return;
504         }
505
506         if (!state->need_priv) {
507                 async_req_done(req);
508                 return;
509         }
510
511         state->wb_req.cmd = WINBINDD_PRIV_PIPE_DIR;
512
513         subreq = wb_int_trans_send(state, state->ev, NULL, state->wb_ctx->fd,
514                                    &state->wb_req);
515         if (async_req_nomem(subreq, req)) {
516                 return;
517         }
518
519         subreq->async.fn = wb_open_pipe_getpriv_done;
520         subreq->async.priv = req;
521 }
522
523 static void wb_open_pipe_getpriv_done(struct async_req *subreq)
524 {
525         struct async_req *req = talloc_get_type_abort(
526                 subreq->async.priv, struct async_req);
527         struct wb_open_pipe_state *state = talloc_get_type_abort(
528                 req->private_data, struct wb_open_pipe_state);
529         struct winbindd_response *wb_resp = NULL;
530         wbcErr wbc_err;
531
532         wbc_err = wb_int_trans_recv(subreq, state, &wb_resp);
533         TALLOC_FREE(subreq);
534         if (!WBC_ERROR_IS_OK(wbc_err)) {
535                 async_req_error(req, wbc_err);
536                 return;
537         }
538
539         close(state->wb_ctx->fd);
540         state->wb_ctx->fd = -1;
541
542         subreq = wb_connect_send(state, state->ev, state->wb_ctx,
543                                  (char *)wb_resp->extra_data.data);
544         TALLOC_FREE(wb_resp);
545         if (async_req_nomem(subreq, req)) {
546                 return;
547         }
548
549         subreq->async.fn = wb_open_pipe_connect_priv_done;
550         subreq->async.priv = req;
551 }
552
553 static void wb_open_pipe_connect_priv_done(struct async_req *subreq)
554 {
555         struct async_req *req = talloc_get_type_abort(
556                 subreq->async.priv, struct async_req);
557         struct wb_open_pipe_state *state = talloc_get_type_abort(
558                 req->private_data, struct wb_open_pipe_state);
559         wbcErr wbc_err;
560
561         wbc_err = wb_connect_recv(subreq);
562         TALLOC_FREE(subreq);
563         if (!WBC_ERROR_IS_OK(wbc_err)) {
564                 async_req_error(req, wbc_err);
565                 return;
566         }
567         state->wb_ctx->is_priv = true;
568         async_req_done(req);
569 }
570
571 static wbcErr wb_open_pipe_recv(struct async_req *req)
572 {
573         return async_req_simple_recv_wbcerr(req);
574 }
575
576 struct wb_trans_state {
577         struct wb_trans_state *prev, *next;
578         struct wb_context *wb_ctx;
579         struct tevent_context *ev;
580         struct winbindd_request *wb_req;
581         struct winbindd_response *wb_resp;
582         int num_retries;
583         bool need_priv;
584 };
585
586 static void wb_trans_connect_done(struct async_req *subreq);
587 static void wb_trans_done(struct async_req *subreq);
588 static void wb_trans_retry_wait_done(struct async_req *subreq);
589
590 static void wb_trigger_trans(struct async_req *req)
591 {
592         struct wb_trans_state *state = talloc_get_type_abort(
593                 req->private_data, struct wb_trans_state);
594         struct async_req *subreq;
595
596         if ((state->wb_ctx->fd == -1)
597             || (state->need_priv && !state->wb_ctx->is_priv)) {
598
599                 subreq = wb_open_pipe_send(state, state->ev, state->wb_ctx,
600                                            state->need_priv);
601                 if (async_req_nomem(subreq, req)) {
602                         return;
603                 }
604                 subreq->async.fn = wb_trans_connect_done;
605                 subreq->async.priv = req;
606                 return;
607         }
608
609         subreq = wb_int_trans_send(state, state->ev, NULL, state->wb_ctx->fd,
610                                    state->wb_req);
611         if (async_req_nomem(subreq, req)) {
612                 return;
613         }
614         subreq->async.fn = wb_trans_done;
615         subreq->async.priv = req;
616 }
617
618 struct async_req *wb_trans_send(TALLOC_CTX *mem_ctx, struct tevent_context *ev,
619                                 struct wb_context *wb_ctx, bool need_priv,
620                                 const struct winbindd_request *wb_req)
621 {
622         struct async_req *result;
623         struct wb_trans_state *state;
624
625         if (!async_req_setup(mem_ctx, &result, &state,
626                              struct wb_trans_state)) {
627                 return NULL;
628         }
629         state->wb_ctx = wb_ctx;
630         state->ev = ev;
631         state->wb_req = winbindd_request_copy(state, wb_req);
632         if (state->wb_req == NULL) {
633                 goto fail;
634         }
635         state->num_retries = 10;
636         state->need_priv = need_priv;
637
638         if (!async_req_enqueue(wb_ctx->queue, ev, result, wb_trigger_trans)) {
639                 goto fail;
640         }
641         return result;
642
643  fail:
644         TALLOC_FREE(result);
645         return NULL;
646 }
647
648 static bool wb_trans_retry(struct async_req *req,
649                            struct wb_trans_state *state,
650                            wbcErr wbc_err)
651 {
652         struct async_req *subreq;
653
654         if (WBC_ERROR_IS_OK(wbc_err)) {
655                 return false;
656         }
657
658         if (wbc_err == WBC_ERR_WINBIND_NOT_AVAILABLE) {
659                 /*
660                  * Winbind not around or we can't connect to the pipe. Fail
661                  * immediately.
662                  */
663                 async_req_error(req, wbc_err);
664                 return true;
665         }
666
667         state->num_retries -= 1;
668         if (state->num_retries == 0) {
669                 async_req_error(req, wbc_err);
670                 return true;
671         }
672
673         /*
674          * The transfer as such failed, retry after one second
675          */
676
677         if (state->wb_ctx->fd != -1) {
678                 close(state->wb_ctx->fd);
679                 state->wb_ctx->fd = -1;
680         }
681
682         subreq = async_wait_send(state, state->ev, timeval_set(1, 0));
683         if (async_req_nomem(subreq, req)) {
684                 return true;
685         }
686
687         subreq->async.fn = wb_trans_retry_wait_done;
688         subreq->async.priv = req;
689         return true;
690 }
691
692 static void wb_trans_retry_wait_done(struct async_req *subreq)
693 {
694         struct async_req *req = talloc_get_type_abort(
695                 subreq->async.priv, struct async_req);
696         struct wb_trans_state *state = talloc_get_type_abort(
697                 req->private_data, struct wb_trans_state);
698         bool ret;
699
700         ret = async_wait_recv(subreq);
701         TALLOC_FREE(subreq);
702         if (ret) {
703                 async_req_error(req, WBC_ERR_UNKNOWN_FAILURE);
704                 return;
705         }
706
707         subreq = wb_open_pipe_send(state, state->ev, state->wb_ctx,
708                                    state->need_priv);
709         if (async_req_nomem(subreq, req)) {
710                 return;
711         }
712         subreq->async.fn = wb_trans_connect_done;
713         subreq->async.priv = req;
714 }
715
716 static void wb_trans_connect_done(struct async_req *subreq)
717 {
718         struct async_req *req = talloc_get_type_abort(
719                 subreq->async.priv, struct async_req);
720         struct wb_trans_state *state = talloc_get_type_abort(
721                 req->private_data, struct wb_trans_state);
722         wbcErr wbc_err;
723
724         wbc_err = wb_open_pipe_recv(subreq);
725         TALLOC_FREE(subreq);
726
727         if (wb_trans_retry(req, state, wbc_err)) {
728                 return;
729         }
730
731         subreq = wb_int_trans_send(state, state->ev, NULL, state->wb_ctx->fd,
732                                    state->wb_req);
733         if (async_req_nomem(subreq, req)) {
734                 return;
735         }
736
737         subreq->async.fn = wb_trans_done;
738         subreq->async.priv = req;
739 }
740
741 static void wb_trans_done(struct async_req *subreq)
742 {
743         struct async_req *req = talloc_get_type_abort(
744                 subreq->async.priv, struct async_req);
745         struct wb_trans_state *state = talloc_get_type_abort(
746                 req->private_data, struct wb_trans_state);
747         wbcErr wbc_err;
748
749         wbc_err = wb_int_trans_recv(subreq, state, &state->wb_resp);
750         TALLOC_FREE(subreq);
751
752         if (wb_trans_retry(req, state, wbc_err)) {
753                 return;
754         }
755
756         async_req_done(req);
757 }
758
759 wbcErr wb_trans_recv(struct async_req *req, TALLOC_CTX *mem_ctx,
760                      struct winbindd_response **presponse)
761 {
762         struct wb_trans_state *state = talloc_get_type_abort(
763                 req->private_data, struct wb_trans_state);
764         wbcErr wbc_err;
765
766         if (async_req_is_wbcerr(req, &wbc_err)) {
767                 return wbc_err;
768         }
769
770         *presponse = talloc_move(mem_ctx, &state->wb_resp);
771         return WBC_ERR_SUCCESS;
772 }