Coverage Report

Created: 2025-07-23 07:04

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