Line data Source code
1 : /*
2 : * Unix SMB/CIFS implementation.
3 : * Client implementation of setting symlinks using reparse points
4 : * Copyright (C) Volker Lendecke 2011
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 "system/filesys.h"
22 : #include "libsmb/libsmb.h"
23 : #include "../lib/util/tevent_ntstatus.h"
24 : #include "async_smb.h"
25 : #include "libsmb/clirap.h"
26 : #include "trans2.h"
27 : #include "libcli/security/secdesc.h"
28 : #include "libcli/security/security.h"
29 : #include "../libcli/smb/smbXcli_base.h"
30 : #include "libcli/smb/reparse.h"
31 :
32 : struct cli_create_reparse_point_state {
33 : struct tevent_context *ev;
34 : struct cli_state *cli;
35 : DATA_BLOB reparse_blob;
36 : uint16_t fnum;
37 : NTSTATUS set_reparse_status;
38 : };
39 :
40 : static void cli_create_reparse_point_opened(struct tevent_req *subreq);
41 : static void cli_create_reparse_point_done(struct tevent_req *subreq);
42 : static void cli_create_reparse_point_doc_done(struct tevent_req *subreq);
43 : static void cli_create_reparse_point_closed(struct tevent_req *subreq);
44 :
45 14 : struct tevent_req *cli_create_reparse_point_send(TALLOC_CTX *mem_ctx,
46 : struct tevent_context *ev,
47 : struct cli_state *cli,
48 : const char *fname,
49 : DATA_BLOB reparse_blob)
50 : {
51 14 : struct tevent_req *req = NULL, *subreq = NULL;
52 14 : struct cli_create_reparse_point_state *state = NULL;
53 :
54 14 : req = tevent_req_create(mem_ctx,
55 : &state,
56 : struct cli_create_reparse_point_state);
57 14 : if (req == NULL) {
58 0 : return NULL;
59 : }
60 14 : state->ev = ev;
61 14 : state->cli = cli;
62 14 : state->reparse_blob = reparse_blob;
63 :
64 : /*
65 : * The create arguments were taken from a Windows->Windows
66 : * symlink create call.
67 : */
68 14 : subreq = cli_ntcreate_send(
69 : state,
70 : ev,
71 : cli,
72 : fname,
73 : 0,
74 : SYNCHRONIZE_ACCESS | DELETE_ACCESS | FILE_READ_ATTRIBUTES |
75 : FILE_WRITE_ATTRIBUTES,
76 : FILE_ATTRIBUTE_NORMAL,
77 : FILE_SHARE_NONE,
78 : FILE_CREATE,
79 : FILE_OPEN_REPARSE_POINT | FILE_SYNCHRONOUS_IO_NONALERT |
80 : FILE_NON_DIRECTORY_FILE,
81 : SMB2_IMPERSONATION_IMPERSONATION,
82 : 0);
83 14 : if (tevent_req_nomem(subreq, req)) {
84 0 : return tevent_req_post(req, ev);
85 : }
86 14 : tevent_req_set_callback(subreq, cli_create_reparse_point_opened, req);
87 14 : return req;
88 : }
89 :
90 14 : static void cli_create_reparse_point_opened(struct tevent_req *subreq)
91 : {
92 0 : struct tevent_req *req =
93 14 : tevent_req_callback_data(subreq, struct tevent_req);
94 0 : struct cli_create_reparse_point_state *state =
95 14 : tevent_req_data(req, struct cli_create_reparse_point_state);
96 0 : NTSTATUS status;
97 :
98 14 : status = cli_ntcreate_recv(subreq, &state->fnum, NULL);
99 14 : TALLOC_FREE(subreq);
100 14 : if (tevent_req_nterror(req, status)) {
101 0 : return;
102 : }
103 :
104 14 : subreq = cli_fsctl_send(state,
105 : state->ev,
106 : state->cli,
107 14 : state->fnum,
108 : FSCTL_SET_REPARSE_POINT,
109 14 : &state->reparse_blob,
110 : 0);
111 14 : if (tevent_req_nomem(subreq, req)) {
112 0 : return;
113 : }
114 14 : tevent_req_set_callback(subreq, cli_create_reparse_point_done, req);
115 : }
116 :
117 14 : static void cli_create_reparse_point_done(struct tevent_req *subreq)
118 : {
119 14 : struct tevent_req *req = tevent_req_callback_data(
120 : subreq, struct tevent_req);
121 0 : struct cli_create_reparse_point_state *state =
122 14 : tevent_req_data(req, struct cli_create_reparse_point_state);
123 :
124 14 : state->set_reparse_status = cli_fsctl_recv(subreq, NULL, NULL);
125 14 : TALLOC_FREE(subreq);
126 :
127 14 : if (NT_STATUS_IS_OK(state->set_reparse_status)) {
128 0 : subreq = cli_close_send(state,
129 : state->ev,
130 : state->cli,
131 0 : state->fnum,
132 : 0);
133 0 : if (tevent_req_nomem(subreq, req)) {
134 0 : return;
135 : }
136 0 : tevent_req_set_callback(subreq,
137 : cli_create_reparse_point_closed,
138 : req);
139 0 : return;
140 : }
141 14 : subreq = cli_nt_delete_on_close_send(
142 14 : state, state->ev, state->cli, state->fnum, true);
143 14 : if (tevent_req_nomem(subreq, req)) {
144 0 : return;
145 : }
146 14 : tevent_req_set_callback(subreq,
147 : cli_create_reparse_point_doc_done,
148 : req);
149 : }
150 :
151 14 : static void cli_create_reparse_point_doc_done(struct tevent_req *subreq)
152 : {
153 14 : struct tevent_req *req = tevent_req_callback_data(
154 : subreq, struct tevent_req);
155 0 : struct cli_create_reparse_point_state *state =
156 14 : tevent_req_data(req, struct cli_create_reparse_point_state);
157 :
158 : /*
159 : * Ignore status, we can't do much anyway in case of failure
160 : */
161 :
162 14 : (void)cli_nt_delete_on_close_recv(subreq);
163 14 : TALLOC_FREE(subreq);
164 :
165 14 : subreq = cli_close_send(state, state->ev, state->cli, state->fnum, 0);
166 14 : if (tevent_req_nomem(subreq, req)) {
167 0 : return;
168 : }
169 14 : tevent_req_set_callback(subreq, cli_create_reparse_point_closed, req);
170 : }
171 :
172 14 : static void cli_create_reparse_point_closed(struct tevent_req *subreq)
173 : {
174 14 : struct tevent_req *req = tevent_req_callback_data(
175 : subreq, struct tevent_req);
176 0 : struct cli_create_reparse_point_state *state =
177 14 : tevent_req_data(req, struct cli_create_reparse_point_state);
178 0 : NTSTATUS status;
179 :
180 14 : status = cli_close_recv(subreq);
181 14 : TALLOC_FREE(subreq);
182 :
183 14 : if (tevent_req_nterror(req, status)) {
184 14 : return;
185 : }
186 14 : if (tevent_req_nterror(req, state->set_reparse_status)) {
187 14 : return;
188 : }
189 0 : tevent_req_done(req);
190 : }
191 :
192 0 : NTSTATUS cli_create_reparse_point_recv(struct tevent_req *req)
193 : {
194 0 : return tevent_req_simple_recv_ntstatus(req);
195 : }
196 :
197 : struct cli_symlink_state {
198 : uint8_t dummy;
199 : };
200 :
201 : static void cli_symlink_done(struct tevent_req *subreq);
202 :
203 14 : struct tevent_req *cli_symlink_send(TALLOC_CTX *mem_ctx,
204 : struct tevent_context *ev,
205 : struct cli_state *cli,
206 : const char *link_target,
207 : const char *newpath,
208 : uint32_t flags)
209 : {
210 14 : struct tevent_req *req = NULL, *subreq = NULL;
211 14 : struct cli_symlink_state *state = NULL;
212 14 : struct reparse_data_buffer reparse_buf = {
213 : .tag = IO_REPARSE_TAG_SYMLINK,
214 : .parsed.lnk.substitute_name =
215 : discard_const_p(char, link_target),
216 : .parsed.lnk.print_name = discard_const_p(char, link_target),
217 : .parsed.lnk.flags = flags,
218 : };
219 0 : uint8_t *buf;
220 0 : ssize_t buflen;
221 :
222 14 : req = tevent_req_create(mem_ctx, &state, struct cli_symlink_state);
223 14 : if (req == NULL) {
224 0 : return NULL;
225 : }
226 :
227 14 : buflen = reparse_data_buffer_marshall(&reparse_buf, NULL, 0);
228 14 : if (buflen == -1) {
229 0 : tevent_req_oom(req);
230 0 : return tevent_req_post(req, ev);
231 : }
232 :
233 14 : buf = talloc_array(state, uint8_t, buflen);
234 14 : if (tevent_req_nomem(buf, req)) {
235 0 : return tevent_req_post(req, ev);
236 : }
237 :
238 14 : buflen = reparse_data_buffer_marshall(&reparse_buf, buf, buflen);
239 14 : if (buflen != talloc_array_length(buf)) {
240 0 : tevent_req_nterror(req, NT_STATUS_INTERNAL_ERROR);
241 0 : return tevent_req_post(req, ev);
242 : }
243 :
244 14 : subreq = cli_create_reparse_point_send(state,
245 : ev,
246 : cli,
247 : newpath,
248 14 : (DATA_BLOB){
249 : .data = buf,
250 : .length = buflen,
251 : });
252 14 : if (tevent_req_nomem(subreq, req)) {
253 0 : return tevent_req_post(req, ev);
254 : }
255 14 : tevent_req_set_callback(subreq, cli_symlink_done, req);
256 14 : return req;
257 : }
258 :
259 14 : static void cli_symlink_done(struct tevent_req *subreq)
260 : {
261 14 : NTSTATUS status = cli_symlink_recv(subreq);
262 14 : tevent_req_simple_finish_ntstatus(subreq, status);
263 14 : }
264 :
265 28 : NTSTATUS cli_symlink_recv(struct tevent_req *req)
266 : {
267 28 : return tevent_req_simple_recv_ntstatus(req);
268 : }
269 :
270 14 : NTSTATUS cli_symlink(struct cli_state *cli, const char *link_target,
271 : const char *newname, uint32_t flags)
272 : {
273 14 : TALLOC_CTX *frame = talloc_stackframe();
274 0 : struct tevent_context *ev;
275 0 : struct tevent_req *req;
276 14 : NTSTATUS status = NT_STATUS_NO_MEMORY;
277 :
278 14 : if (smbXcli_conn_has_async_calls(cli->conn)) {
279 0 : status = NT_STATUS_INVALID_PARAMETER;
280 0 : goto fail;
281 : }
282 14 : ev = samba_tevent_context_init(frame);
283 14 : if (ev == NULL) {
284 0 : goto fail;
285 : }
286 14 : req = cli_symlink_send(frame, ev, cli, link_target, newname, flags);
287 14 : if (req == NULL) {
288 0 : goto fail;
289 : }
290 14 : if (!tevent_req_poll_ntstatus(req, ev, &status)) {
291 0 : goto fail;
292 : }
293 14 : status = cli_symlink_recv(req);
294 14 : fail:
295 14 : TALLOC_FREE(frame);
296 14 : return status;
297 : }
298 :
299 : struct cli_get_reparse_data_state {
300 : struct tevent_context *ev;
301 : struct cli_state *cli;
302 : uint16_t fnum;
303 :
304 : NTSTATUS get_reparse_status;
305 : uint8_t *data;
306 : uint32_t datalen;
307 : };
308 :
309 : static void cli_get_reparse_data_opened(struct tevent_req *subreq);
310 : static void cli_get_reparse_data_done(struct tevent_req *subreq);
311 : static void cli_get_reparse_data_closed(struct tevent_req *subreq);
312 :
313 0 : struct tevent_req *cli_get_reparse_data_send(TALLOC_CTX *mem_ctx,
314 : struct tevent_context *ev,
315 : struct cli_state *cli,
316 : const char *fname)
317 : {
318 0 : struct tevent_req *req = NULL, *subreq = NULL;
319 0 : struct cli_get_reparse_data_state *state = NULL;
320 :
321 0 : req = tevent_req_create(mem_ctx,
322 : &state,
323 : struct cli_get_reparse_data_state);
324 0 : if (req == NULL) {
325 0 : return NULL;
326 : }
327 0 : state->ev = ev;
328 0 : state->cli = cli;
329 :
330 0 : subreq = cli_ntcreate_send(state,
331 : ev,
332 : cli,
333 : fname,
334 : 0,
335 : FILE_READ_ATTRIBUTES | FILE_READ_EA,
336 : 0,
337 : FILE_SHARE_READ | FILE_SHARE_WRITE |
338 : FILE_SHARE_DELETE,
339 : FILE_OPEN,
340 : FILE_OPEN_REPARSE_POINT,
341 : SMB2_IMPERSONATION_IMPERSONATION,
342 : 0);
343 0 : if (tevent_req_nomem(subreq, req)) {
344 0 : return tevent_req_post(req, ev);
345 : }
346 0 : tevent_req_set_callback(subreq, cli_get_reparse_data_opened, req);
347 0 : return req;
348 : }
349 :
350 0 : static void cli_get_reparse_data_opened(struct tevent_req *subreq)
351 : {
352 0 : struct tevent_req *req =
353 0 : tevent_req_callback_data(subreq, struct tevent_req);
354 0 : struct cli_get_reparse_data_state *state =
355 0 : tevent_req_data(req, struct cli_get_reparse_data_state);
356 0 : NTSTATUS status;
357 :
358 0 : status = cli_ntcreate_recv(subreq, &state->fnum, NULL);
359 0 : TALLOC_FREE(subreq);
360 0 : if (tevent_req_nterror(req, status)) {
361 0 : return;
362 : }
363 :
364 0 : subreq = cli_fsctl_send(state,
365 : state->ev,
366 : state->cli,
367 0 : state->fnum,
368 : FSCTL_GET_REPARSE_POINT,
369 : NULL,
370 : 65536);
371 :
372 0 : if (tevent_req_nomem(subreq, req)) {
373 0 : return;
374 : }
375 0 : tevent_req_set_callback(subreq, cli_get_reparse_data_done, req);
376 : }
377 :
378 0 : static void cli_get_reparse_data_done(struct tevent_req *subreq)
379 : {
380 0 : struct tevent_req *req =
381 0 : tevent_req_callback_data(subreq, struct tevent_req);
382 0 : struct cli_get_reparse_data_state *state =
383 0 : tevent_req_data(req, struct cli_get_reparse_data_state);
384 0 : DATA_BLOB out = {
385 : .data = NULL,
386 : };
387 :
388 0 : state->get_reparse_status = cli_fsctl_recv(subreq, state, &out);
389 0 : TALLOC_FREE(subreq);
390 :
391 0 : if (NT_STATUS_IS_OK(state->get_reparse_status)) {
392 0 : state->data = out.data;
393 0 : state->datalen = out.length;
394 : }
395 :
396 0 : subreq = cli_close_send(state, state->ev, state->cli, state->fnum, 0);
397 0 : if (tevent_req_nomem(subreq, req)) {
398 0 : return;
399 : }
400 0 : tevent_req_set_callback(subreq, cli_get_reparse_data_closed, req);
401 : }
402 :
403 0 : static void cli_get_reparse_data_closed(struct tevent_req *subreq)
404 : {
405 0 : struct tevent_req *req =
406 0 : tevent_req_callback_data(subreq, struct tevent_req);
407 0 : struct cli_get_reparse_data_state *state =
408 0 : tevent_req_data(req, struct cli_get_reparse_data_state);
409 0 : NTSTATUS status;
410 :
411 0 : status = cli_close_recv(subreq);
412 0 : TALLOC_FREE(subreq);
413 0 : if (tevent_req_nterror(req, status)) {
414 0 : return;
415 : }
416 0 : if (tevent_req_nterror(req, state->get_reparse_status)) {
417 0 : return;
418 : }
419 0 : tevent_req_done(req);
420 : }
421 :
422 0 : NTSTATUS cli_get_reparse_data_recv(struct tevent_req *req,
423 : TALLOC_CTX *mem_ctx,
424 : uint8_t **_data,
425 : uint32_t *_datalen)
426 : {
427 0 : struct cli_get_reparse_data_state *state =
428 0 : tevent_req_data(req, struct cli_get_reparse_data_state);
429 0 : NTSTATUS status;
430 :
431 0 : if (tevent_req_is_nterror(req, &status)) {
432 0 : return status;
433 : }
434 :
435 0 : *_data = talloc_move(mem_ctx, &state->data);
436 0 : *_datalen = state->datalen;
437 :
438 0 : tevent_req_received(req);
439 :
440 0 : return NT_STATUS_OK;
441 : }
442 :
443 0 : NTSTATUS cli_get_reparse_data(struct cli_state *cli,
444 : const char *fname,
445 : TALLOC_CTX *mem_ctx,
446 : uint8_t **_data,
447 : uint32_t *_datalen)
448 : {
449 0 : TALLOC_CTX *frame = talloc_stackframe();
450 0 : struct tevent_context *ev;
451 0 : struct tevent_req *req;
452 0 : NTSTATUS status = NT_STATUS_NO_MEMORY;
453 :
454 0 : if (smbXcli_conn_has_async_calls(cli->conn)) {
455 0 : status = NT_STATUS_INVALID_PARAMETER;
456 0 : goto fail;
457 : }
458 0 : ev = samba_tevent_context_init(frame);
459 0 : if (ev == NULL) {
460 0 : goto fail;
461 : }
462 0 : req = cli_get_reparse_data_send(frame, ev, cli, fname);
463 0 : if (req == NULL) {
464 0 : goto fail;
465 : }
466 0 : if (!tevent_req_poll_ntstatus(req, ev, &status)) {
467 0 : goto fail;
468 : }
469 0 : status = cli_get_reparse_data_recv(req, mem_ctx, _data, _datalen);
470 0 : fail:
471 0 : TALLOC_FREE(frame);
472 0 : return status;
473 : }
474 :
475 : struct cli_readlink_state {
476 : struct tevent_context *ev;
477 : struct cli_state *cli;
478 : uint16_t fnum;
479 :
480 : uint16_t setup[4];
481 : uint8_t *data;
482 : uint32_t num_data;
483 : char *target;
484 : };
485 :
486 : static void cli_readlink_posix1_done(struct tevent_req *subreq);
487 : static void cli_readlink_got_reparse_data(struct tevent_req *subreq);
488 :
489 16 : struct tevent_req *cli_readlink_send(TALLOC_CTX *mem_ctx,
490 : struct tevent_context *ev,
491 : struct cli_state *cli,
492 : const char *fname)
493 : {
494 0 : struct tevent_req *req, *subreq;
495 0 : struct cli_readlink_state *state;
496 :
497 16 : req = tevent_req_create(mem_ctx, &state, struct cli_readlink_state);
498 16 : if (req == NULL) {
499 0 : return NULL;
500 : }
501 16 : state->ev = ev;
502 16 : state->cli = cli;
503 :
504 16 : if (cli->requested_posix_capabilities != 0) {
505 : /*
506 : * Only happens for negotiated SMB1 posix caps
507 : */
508 16 : subreq = cli_posix_readlink_send(state, ev, cli, fname);
509 16 : if (tevent_req_nomem(subreq, req)) {
510 0 : return tevent_req_post(req, ev);
511 : }
512 16 : tevent_req_set_callback(subreq, cli_readlink_posix1_done, req);
513 16 : return req;
514 : }
515 :
516 0 : subreq = cli_get_reparse_data_send(state, ev, cli, fname);
517 0 : if (tevent_req_nomem(subreq, req)) {
518 0 : return tevent_req_post(req, ev);
519 : }
520 0 : tevent_req_set_callback(subreq, cli_readlink_got_reparse_data, req);
521 0 : return req;
522 : }
523 :
524 16 : static void cli_readlink_posix1_done(struct tevent_req *subreq)
525 : {
526 16 : struct tevent_req *req = tevent_req_callback_data(
527 : subreq, struct tevent_req);
528 16 : struct cli_readlink_state *state = tevent_req_data(
529 : req, struct cli_readlink_state);
530 0 : NTSTATUS status;
531 :
532 16 : status = cli_posix_readlink_recv(subreq, state, &state->target);
533 16 : TALLOC_FREE(subreq);
534 16 : if (tevent_req_nterror(req, status)) {
535 0 : return;
536 : }
537 16 : tevent_req_done(req);
538 : }
539 :
540 0 : static void cli_readlink_got_reparse_data(struct tevent_req *subreq)
541 : {
542 0 : struct tevent_req *req = tevent_req_callback_data(
543 : subreq, struct tevent_req);
544 0 : struct cli_readlink_state *state = tevent_req_data(
545 : req, struct cli_readlink_state);
546 0 : NTSTATUS status;
547 :
548 0 : status = cli_get_reparse_data_recv(subreq,
549 : state,
550 : &state->data,
551 : &state->num_data);
552 0 : TALLOC_FREE(subreq);
553 0 : if (tevent_req_nterror(req, status)) {
554 0 : return;
555 : }
556 0 : tevent_req_done(req);
557 : }
558 :
559 16 : NTSTATUS cli_readlink_recv(struct tevent_req *req, TALLOC_CTX *mem_ctx,
560 : char **psubstitute_name, char **pprint_name,
561 : uint32_t *pflags)
562 : {
563 16 : struct cli_readlink_state *state = tevent_req_data(
564 : req, struct cli_readlink_state);
565 16 : struct reparse_data_buffer buf = {
566 : .tag = 0,
567 : };
568 0 : NTSTATUS status;
569 :
570 16 : if (tevent_req_is_nterror(req, &status)) {
571 0 : return status;
572 : }
573 :
574 16 : if (state->target != NULL) {
575 : /*
576 : * SMB1 posix version
577 : */
578 16 : if (psubstitute_name != NULL) {
579 16 : *psubstitute_name = talloc_move(
580 : mem_ctx, &state->target);
581 : }
582 16 : if (pprint_name != NULL) {
583 0 : *pprint_name = NULL;
584 : }
585 16 : if (pflags != NULL) {
586 0 : *pflags = 0;
587 : }
588 16 : return NT_STATUS_OK;
589 : }
590 :
591 0 : status = reparse_data_buffer_parse(state,
592 : &buf,
593 0 : state->data,
594 0 : state->num_data);
595 0 : if (!NT_STATUS_IS_OK(status)) {
596 0 : return NT_STATUS_INVALID_NETWORK_RESPONSE;
597 : }
598 0 : if (buf.tag != IO_REPARSE_TAG_SYMLINK) {
599 0 : return NT_STATUS_INVALID_NETWORK_RESPONSE;
600 : }
601 :
602 0 : if (psubstitute_name != NULL) {
603 0 : *psubstitute_name =
604 0 : talloc_move(mem_ctx, &buf.parsed.lnk.substitute_name);
605 : }
606 :
607 0 : if (pprint_name != NULL) {
608 0 : *pprint_name =
609 0 : talloc_move(mem_ctx, &buf.parsed.lnk.print_name);
610 : }
611 :
612 0 : if (pflags != NULL) {
613 0 : *pflags = buf.parsed.lnk.flags;
614 : }
615 :
616 0 : tevent_req_received(req);
617 :
618 0 : return NT_STATUS_OK;
619 : }
620 :
621 16 : NTSTATUS cli_readlink(struct cli_state *cli, const char *fname,
622 : TALLOC_CTX *mem_ctx, char **psubstitute_name,
623 : char **pprint_name, uint32_t *pflags)
624 : {
625 16 : TALLOC_CTX *frame = talloc_stackframe();
626 0 : struct tevent_context *ev;
627 0 : struct tevent_req *req;
628 16 : NTSTATUS status = NT_STATUS_NO_MEMORY;
629 :
630 16 : if (smbXcli_conn_has_async_calls(cli->conn)) {
631 0 : status = NT_STATUS_INVALID_PARAMETER;
632 0 : goto fail;
633 : }
634 16 : ev = samba_tevent_context_init(frame);
635 16 : if (ev == NULL) {
636 0 : goto fail;
637 : }
638 16 : req = cli_readlink_send(frame, ev, cli, fname);
639 16 : if (req == NULL) {
640 0 : goto fail;
641 : }
642 16 : if (!tevent_req_poll_ntstatus(req, ev, &status)) {
643 0 : goto fail;
644 : }
645 16 : status = cli_readlink_recv(req, mem_ctx, psubstitute_name,
646 : pprint_name, pflags);
647 16 : fail:
648 16 : TALLOC_FREE(frame);
649 16 : return status;
650 : }
|