Merge branch 'master' of ssh://jra@git.samba.org/data/git/samba
[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         subreq->async.fn = wbc_connect_connected;
228         subreq->async.private_data = result;
229
230         if (!tevent_req_set_endtime(subreq, ev, timeval_current_ofs(30, 0))) {
231                 goto nomem;
232         }
233
234         return result;
235
236  nomem:
237         wbc_err = WBC_ERR_NO_MEMORY;
238  post_status:
239         if (async_post_error(result, ev, wbc_err)) {
240                 return result;
241         }
242         TALLOC_FREE(result);
243         return NULL;
244 }
245
246 static void wbc_connect_connected(struct tevent_req *subreq)
247 {
248         struct async_req *req = talloc_get_type_abort(
249                 subreq->async.private_data, struct async_req);
250         int res, err;
251
252         res = async_connect_recv(subreq, &err);
253         TALLOC_FREE(subreq);
254         if (res == -1) {
255                 async_req_error(req, map_wbc_err_from_errno(err));
256                 return;
257         }
258         async_req_done(req);
259 }
260
261 static wbcErr wb_connect_recv(struct async_req *req)
262 {
263         return async_req_simple_recv_wbcerr(req);
264 }
265
266 static struct winbindd_request *winbindd_request_copy(
267         TALLOC_CTX *mem_ctx,
268         const struct winbindd_request *req)
269 {
270         struct winbindd_request *result;
271
272         result = (struct winbindd_request *)TALLOC_MEMDUP(
273                 mem_ctx, req, sizeof(struct winbindd_request));
274         if (result == NULL) {
275                 return NULL;
276         }
277
278         if (result->extra_len == 0) {
279                 return result;
280         }
281
282         result->extra_data.data = (char *)TALLOC_MEMDUP(
283                 result, result->extra_data.data, result->extra_len);
284         if (result->extra_data.data == NULL) {
285                 TALLOC_FREE(result);
286                 return NULL;
287         }
288         return result;
289 }
290
291 struct wb_int_trans_state {
292         struct tevent_context *ev;
293         int fd;
294         struct winbindd_request *wb_req;
295         struct winbindd_response *wb_resp;
296 };
297
298 static void wb_int_trans_write_done(struct async_req *subreq);
299 static void wb_int_trans_read_done(struct async_req *subreq);
300
301 static struct async_req *wb_int_trans_send(TALLOC_CTX *mem_ctx,
302                                            struct tevent_context *ev, int fd,
303                                            struct winbindd_request *wb_req)
304 {
305         struct async_req *result;
306         struct async_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, state->fd, state->wb_req);
330         if (subreq == NULL) {
331                 goto fail;
332         }
333         subreq->async.fn = wb_int_trans_write_done;
334         subreq->async.priv = 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 async_req *subreq)
344 {
345         struct async_req *req = talloc_get_type_abort(
346                 subreq->async.priv, 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 (subreq == NULL) {
360                 async_req_error(req, WBC_ERR_NO_MEMORY);
361         }
362         subreq->async.fn = wb_int_trans_read_done;
363         subreq->async.priv = req;
364 }
365
366 static void wb_int_trans_read_done(struct async_req *subreq)
367 {
368         struct async_req *req = talloc_get_type_abort(
369                 subreq->async.priv, struct async_req);
370         struct wb_int_trans_state *state = talloc_get_type_abort(
371                 req->private_data, struct wb_int_trans_state);
372         wbcErr wbc_err;
373
374         wbc_err = wb_resp_read_recv(subreq, state, &state->wb_resp);
375         TALLOC_FREE(subreq);
376         if (!WBC_ERROR_IS_OK(wbc_err)) {
377                 async_req_error(req, wbc_err);
378                 return;
379         }
380
381         async_req_done(req);
382 }
383
384 static wbcErr wb_int_trans_recv(struct async_req *req,
385                                 TALLOC_CTX *mem_ctx,
386                                 struct winbindd_response **presponse)
387 {
388         struct wb_int_trans_state *state = talloc_get_type_abort(
389                 req->private_data, struct wb_int_trans_state);
390         wbcErr wbc_err;
391
392         if (async_req_is_wbcerr(req, &wbc_err)) {
393                 return wbc_err;
394         }
395
396         *presponse = talloc_move(mem_ctx, &state->wb_resp);
397         return WBC_ERR_SUCCESS;
398 }
399
400 static const char *winbindd_socket_dir(void)
401 {
402 #ifdef SOCKET_WRAPPER
403         const char *env_dir;
404
405         env_dir = getenv(WINBINDD_SOCKET_DIR_ENVVAR);
406         if (env_dir) {
407                 return env_dir;
408         }
409 #endif
410
411         return WINBINDD_SOCKET_DIR;
412 }
413
414 struct wb_open_pipe_state {
415         struct wb_context *wb_ctx;
416         struct tevent_context *ev;
417         bool need_priv;
418         struct winbindd_request wb_req;
419 };
420
421 static void wb_open_pipe_connect_nonpriv_done(struct async_req *subreq);
422 static void wb_open_pipe_ping_done(struct async_req *subreq);
423 static void wb_open_pipe_getpriv_done(struct async_req *subreq);
424 static void wb_open_pipe_connect_priv_done(struct async_req *subreq);
425
426 static struct async_req *wb_open_pipe_send(TALLOC_CTX *mem_ctx,
427                                            struct tevent_context *ev,
428                                            struct wb_context *wb_ctx,
429                                            bool need_priv)
430 {
431         struct async_req *result;
432         struct async_req *subreq;
433         struct wb_open_pipe_state *state;
434
435         if (!async_req_setup(mem_ctx, &result, &state,
436                              struct wb_open_pipe_state)) {
437                 return NULL;
438         }
439         state->wb_ctx = wb_ctx;
440         state->ev = ev;
441         state->need_priv = need_priv;
442
443         if (wb_ctx->fd != -1) {
444                 close(wb_ctx->fd);
445                 wb_ctx->fd = -1;
446         }
447
448         subreq = wb_connect_send(state, ev, wb_ctx, winbindd_socket_dir());
449         if (subreq == NULL) {
450                 goto fail;
451         }
452
453         subreq->async.fn = wb_open_pipe_connect_nonpriv_done;
454         subreq->async.priv = result;
455         return result;
456
457  fail:
458         TALLOC_FREE(result);
459         return NULL;
460 }
461
462 static void wb_open_pipe_connect_nonpriv_done(struct async_req *subreq)
463 {
464         struct async_req *req = talloc_get_type_abort(
465                 subreq->async.priv, struct async_req);
466         struct wb_open_pipe_state *state = talloc_get_type_abort(
467                 req->private_data, struct wb_open_pipe_state);
468         wbcErr wbc_err;
469
470         wbc_err = wb_connect_recv(subreq);
471         TALLOC_FREE(subreq);
472         if (!WBC_ERROR_IS_OK(wbc_err)) {
473                 state->wb_ctx->is_priv = true;
474                 async_req_error(req, wbc_err);
475                 return;
476         }
477
478         ZERO_STRUCT(state->wb_req);
479         state->wb_req.cmd = WINBINDD_INTERFACE_VERSION;
480
481         subreq = wb_int_trans_send(state, state->ev, state->wb_ctx->fd,
482                                    &state->wb_req);
483         if (async_req_nomem(subreq, req)) {
484                 return;
485         }
486
487         subreq->async.fn = wb_open_pipe_ping_done;
488         subreq->async.priv = req;
489 }
490
491 static void wb_open_pipe_ping_done(struct async_req *subreq)
492 {
493         struct async_req *req = talloc_get_type_abort(
494                 subreq->async.priv, struct async_req);
495         struct wb_open_pipe_state *state = talloc_get_type_abort(
496                 req->private_data, struct wb_open_pipe_state);
497         struct winbindd_response *wb_resp;
498         wbcErr wbc_err;
499
500         wbc_err = wb_int_trans_recv(subreq, state, &wb_resp);
501         TALLOC_FREE(subreq);
502         if (!WBC_ERROR_IS_OK(wbc_err)) {
503                 async_req_error(req, wbc_err);
504                 return;
505         }
506
507         if (!state->need_priv) {
508                 async_req_done(req);
509                 return;
510         }
511
512         state->wb_req.cmd = WINBINDD_PRIV_PIPE_DIR;
513
514         subreq = wb_int_trans_send(state, state->ev, state->wb_ctx->fd,
515                                    &state->wb_req);
516         if (async_req_nomem(subreq, req)) {
517                 return;
518         }
519
520         subreq->async.fn = wb_open_pipe_getpriv_done;
521         subreq->async.priv = req;
522 }
523
524 static void wb_open_pipe_getpriv_done(struct async_req *subreq)
525 {
526         struct async_req *req = talloc_get_type_abort(
527                 subreq->async.priv, struct async_req);
528         struct wb_open_pipe_state *state = talloc_get_type_abort(
529                 req->private_data, struct wb_open_pipe_state);
530         struct winbindd_response *wb_resp = NULL;
531         wbcErr wbc_err;
532
533         wbc_err = wb_int_trans_recv(subreq, state, &wb_resp);
534         TALLOC_FREE(subreq);
535         if (!WBC_ERROR_IS_OK(wbc_err)) {
536                 async_req_error(req, wbc_err);
537                 return;
538         }
539
540         close(state->wb_ctx->fd);
541         state->wb_ctx->fd = -1;
542
543         subreq = wb_connect_send(state, state->ev, state->wb_ctx,
544                                  (char *)wb_resp->extra_data.data);
545         TALLOC_FREE(wb_resp);
546         if (async_req_nomem(subreq, req)) {
547                 return;
548         }
549
550         subreq->async.fn = wb_open_pipe_connect_priv_done;
551         subreq->async.priv = req;
552 }
553
554 static void wb_open_pipe_connect_priv_done(struct async_req *subreq)
555 {
556         struct async_req *req = talloc_get_type_abort(
557                 subreq->async.priv, struct async_req);
558         struct wb_open_pipe_state *state = talloc_get_type_abort(
559                 req->private_data, struct wb_open_pipe_state);
560         wbcErr wbc_err;
561
562         wbc_err = wb_connect_recv(subreq);
563         TALLOC_FREE(subreq);
564         if (!WBC_ERROR_IS_OK(wbc_err)) {
565                 async_req_error(req, wbc_err);
566                 return;
567         }
568         state->wb_ctx->is_priv = true;
569         async_req_done(req);
570 }
571
572 static wbcErr wb_open_pipe_recv(struct async_req *req)
573 {
574         return async_req_simple_recv_wbcerr(req);
575 }
576
577 struct wb_trans_state {
578         struct wb_trans_state *prev, *next;
579         struct wb_context *wb_ctx;
580         struct tevent_context *ev;
581         struct winbindd_request *wb_req;
582         struct winbindd_response *wb_resp;
583         int num_retries;
584         bool need_priv;
585 };
586
587 static void wb_trans_connect_done(struct async_req *subreq);
588 static void wb_trans_done(struct async_req *subreq);
589 static void wb_trans_retry_wait_done(struct async_req *subreq);
590
591 static void wb_trigger_trans(struct async_req *req)
592 {
593         struct wb_trans_state *state = talloc_get_type_abort(
594                 req->private_data, struct wb_trans_state);
595         struct async_req *subreq;
596
597         if ((state->wb_ctx->fd == -1)
598             || (state->need_priv && !state->wb_ctx->is_priv)) {
599
600                 subreq = wb_open_pipe_send(state, state->ev, state->wb_ctx,
601                                            state->need_priv);
602                 if (async_req_nomem(subreq, req)) {
603                         return;
604                 }
605                 subreq->async.fn = wb_trans_connect_done;
606                 subreq->async.priv = req;
607                 return;
608         }
609
610         subreq = wb_int_trans_send(state, state->ev, state->wb_ctx->fd,
611                                    state->wb_req);
612         if (async_req_nomem(subreq, req)) {
613                 return;
614         }
615         subreq->async.fn = wb_trans_done;
616         subreq->async.priv = req;
617 }
618
619 struct async_req *wb_trans_send(TALLOC_CTX *mem_ctx, struct tevent_context *ev,
620                                 struct wb_context *wb_ctx, bool need_priv,
621                                 const struct winbindd_request *wb_req)
622 {
623         struct async_req *result;
624         struct wb_trans_state *state;
625
626         if (!async_req_setup(mem_ctx, &result, &state,
627                              struct wb_trans_state)) {
628                 return NULL;
629         }
630         state->wb_ctx = wb_ctx;
631         state->ev = ev;
632         state->wb_req = winbindd_request_copy(state, wb_req);
633         if (state->wb_req == NULL) {
634                 goto fail;
635         }
636         state->num_retries = 10;
637         state->need_priv = need_priv;
638
639         if (!async_req_enqueue(wb_ctx->queue, ev, result, wb_trigger_trans)) {
640                 goto fail;
641         }
642         return result;
643
644  fail:
645         TALLOC_FREE(result);
646         return NULL;
647 }
648
649 static bool wb_trans_retry(struct async_req *req,
650                            struct wb_trans_state *state,
651                            wbcErr wbc_err)
652 {
653         struct async_req *subreq;
654
655         if (WBC_ERROR_IS_OK(wbc_err)) {
656                 return false;
657         }
658
659         if (wbc_err == WBC_ERR_WINBIND_NOT_AVAILABLE) {
660                 /*
661                  * Winbind not around or we can't connect to the pipe. Fail
662                  * immediately.
663                  */
664                 async_req_error(req, wbc_err);
665                 return true;
666         }
667
668         state->num_retries -= 1;
669         if (state->num_retries == 0) {
670                 async_req_error(req, wbc_err);
671                 return true;
672         }
673
674         /*
675          * The transfer as such failed, retry after one second
676          */
677
678         if (state->wb_ctx->fd != -1) {
679                 close(state->wb_ctx->fd);
680                 state->wb_ctx->fd = -1;
681         }
682
683         subreq = async_wait_send(state, state->ev, timeval_set(1, 0));
684         if (async_req_nomem(subreq, req)) {
685                 return true;
686         }
687
688         subreq->async.fn = wb_trans_retry_wait_done;
689         subreq->async.priv = req;
690         return true;
691 }
692
693 static void wb_trans_retry_wait_done(struct async_req *subreq)
694 {
695         struct async_req *req = talloc_get_type_abort(
696                 subreq->async.priv, struct async_req);
697         struct wb_trans_state *state = talloc_get_type_abort(
698                 req->private_data, struct wb_trans_state);
699         bool ret;
700
701         ret = async_wait_recv(subreq);
702         TALLOC_FREE(subreq);
703         if (ret) {
704                 async_req_error(req, WBC_ERR_UNKNOWN_FAILURE);
705                 return;
706         }
707
708         subreq = wb_open_pipe_send(state, state->ev, state->wb_ctx,
709                                    state->need_priv);
710         if (async_req_nomem(subreq, req)) {
711                 return;
712         }
713         subreq->async.fn = wb_trans_connect_done;
714         subreq->async.priv = req;
715 }
716
717 static void wb_trans_connect_done(struct async_req *subreq)
718 {
719         struct async_req *req = talloc_get_type_abort(
720                 subreq->async.priv, struct async_req);
721         struct wb_trans_state *state = talloc_get_type_abort(
722                 req->private_data, struct wb_trans_state);
723         wbcErr wbc_err;
724
725         wbc_err = wb_open_pipe_recv(subreq);
726         TALLOC_FREE(subreq);
727
728         if (wb_trans_retry(req, state, wbc_err)) {
729                 return;
730         }
731
732         subreq = wb_int_trans_send(state, state->ev, state->wb_ctx->fd,
733                                    state->wb_req);
734         if (async_req_nomem(subreq, req)) {
735                 return;
736         }
737
738         subreq->async.fn = wb_trans_done;
739         subreq->async.priv = req;
740 }
741
742 static void wb_trans_done(struct async_req *subreq)
743 {
744         struct async_req *req = talloc_get_type_abort(
745                 subreq->async.priv, struct async_req);
746         struct wb_trans_state *state = talloc_get_type_abort(
747                 req->private_data, struct wb_trans_state);
748         wbcErr wbc_err;
749
750         wbc_err = wb_int_trans_recv(subreq, state, &state->wb_resp);
751         TALLOC_FREE(subreq);
752
753         if (wb_trans_retry(req, state, wbc_err)) {
754                 return;
755         }
756
757         async_req_done(req);
758 }
759
760 wbcErr wb_trans_recv(struct async_req *req, TALLOC_CTX *mem_ctx,
761                      struct winbindd_response **presponse)
762 {
763         struct wb_trans_state *state = talloc_get_type_abort(
764                 req->private_data, struct wb_trans_state);
765         wbcErr wbc_err;
766
767         if (async_req_is_wbcerr(req, &wbc_err)) {
768                 return wbc_err;
769         }
770
771         *presponse = talloc_move(mem_ctx, &state->wb_resp);
772         return WBC_ERR_SUCCESS;
773 }