Line data Source code
1 : /*
2 : Unix SMB/CIFS implementation.
3 : client connect/disconnect routines
4 : Copyright (C) Andrew Tridgell 1994-1998
5 : Copyright (C) Gerald (Jerry) Carter 2004
6 : Copyright (C) Jeremy Allison 2007-2009
7 :
8 : This program is free software; you can redistribute it and/or modify
9 : it under the terms of the GNU General Public License as published by
10 : the Free Software Foundation; either version 3 of the License, or
11 : (at your option) any later version.
12 :
13 : This program is distributed in the hope that it will be useful,
14 : but WITHOUT ANY WARRANTY; without even the implied warranty of
15 : MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 : GNU General Public License for more details.
17 :
18 : You should have received a copy of the GNU General Public License
19 : along with this program. If not, see <http://www.gnu.org/licenses/>.
20 : */
21 :
22 : #include "includes.h"
23 : #include "libsmb/libsmb.h"
24 : #include "libsmb/clirap.h"
25 : #include "msdfs.h"
26 : #include "trans2.h"
27 : #include "libsmb/nmblib.h"
28 : #include "../libcli/smb/smbXcli_base.h"
29 : #include "auth/credentials/credentials.h"
30 :
31 : /********************************************************************
32 : Important point.
33 :
34 : DFS paths are *always* of the form \server\share\<pathname> (the \ characters
35 : are not C escaped here).
36 :
37 : - but if we're using POSIX paths then <pathname> may contain
38 : '/' separators, not '\\' separators. So cope with '\\' or '/'
39 : as a separator when looking at the pathname part.... JRA.
40 : ********************************************************************/
41 :
42 : /********************************************************************
43 : Ensure a connection is encrypted.
44 : ********************************************************************/
45 :
46 12 : static NTSTATUS cli_cm_force_encryption_creds(struct cli_state *c,
47 : struct cli_credentials *creds,
48 : const char *sharename)
49 : {
50 : uint16_t major, minor;
51 : uint32_t caplow, caphigh;
52 : NTSTATUS status;
53 12 : bool temp_ipc = false;
54 :
55 12 : if (smbXcli_conn_protocol(c->conn) >= PROTOCOL_SMB2_02) {
56 12 : status = smb2cli_session_encryption_on(c->smb2.session);
57 12 : if (NT_STATUS_EQUAL(status,NT_STATUS_NOT_SUPPORTED)) {
58 0 : d_printf("Encryption required and "
59 : "server doesn't support "
60 : "SMB3 encryption - failing connect\n");
61 12 : } else if (!NT_STATUS_IS_OK(status)) {
62 0 : d_printf("Encryption required and "
63 : "setup failed with error %s.\n",
64 : nt_errstr(status));
65 : }
66 12 : return status;
67 : }
68 :
69 0 : if (!SERVER_HAS_UNIX_CIFS(c)) {
70 0 : d_printf("Encryption required and "
71 : "server that doesn't support "
72 : "UNIX extensions - failing connect\n");
73 0 : return NT_STATUS_NOT_SUPPORTED;
74 : }
75 :
76 0 : if (c->smb1.tcon == NULL) {
77 0 : status = cli_tree_connect_creds(c, "IPC$", "IPC", creds);
78 0 : if (!NT_STATUS_IS_OK(status)) {
79 0 : d_printf("Encryption required and "
80 : "can't connect to IPC$ to check "
81 : "UNIX CIFS extensions.\n");
82 0 : return NT_STATUS_UNKNOWN_REVISION;
83 : }
84 0 : temp_ipc = true;
85 : }
86 :
87 0 : status = cli_unix_extensions_version(c, &major, &minor, &caplow,
88 : &caphigh);
89 0 : if (!NT_STATUS_IS_OK(status)) {
90 0 : d_printf("Encryption required and "
91 : "can't get UNIX CIFS extensions "
92 : "version from server.\n");
93 0 : if (temp_ipc) {
94 0 : cli_tdis(c);
95 : }
96 0 : return NT_STATUS_UNKNOWN_REVISION;
97 : }
98 :
99 0 : if (!(caplow & CIFS_UNIX_TRANSPORT_ENCRYPTION_CAP)) {
100 0 : d_printf("Encryption required and "
101 : "share %s doesn't support "
102 : "encryption.\n", sharename);
103 0 : if (temp_ipc) {
104 0 : cli_tdis(c);
105 : }
106 0 : return NT_STATUS_UNSUPPORTED_COMPRESSION;
107 : }
108 :
109 0 : status = cli_smb1_setup_encryption(c, creds);
110 0 : if (!NT_STATUS_IS_OK(status)) {
111 0 : d_printf("Encryption required and "
112 : "setup failed with error %s.\n",
113 : nt_errstr(status));
114 0 : if (temp_ipc) {
115 0 : cli_tdis(c);
116 : }
117 0 : return status;
118 : }
119 :
120 0 : if (temp_ipc) {
121 0 : cli_tdis(c);
122 : }
123 0 : return NT_STATUS_OK;
124 : }
125 :
126 : /********************************************************************
127 : Return a connection to a server.
128 : ********************************************************************/
129 :
130 1310 : static NTSTATUS do_connect(TALLOC_CTX *ctx,
131 : const char *server,
132 : const char *share,
133 : struct cli_credentials *creds,
134 : const struct sockaddr_storage *dest_ss,
135 : int port,
136 : int name_type,
137 : struct cli_state **pcli)
138 : {
139 1310 : struct cli_state *c = NULL;
140 : char *servicename;
141 : char *sharename;
142 : char *newserver, *newshare;
143 : NTSTATUS status;
144 1310 : int flags = 0;
145 1310 : enum protocol_types protocol = PROTOCOL_NONE;
146 702 : enum smb_signing_setting signing_state =
147 608 : cli_credentials_get_smb_signing(creds);
148 702 : enum smb_encryption_setting encryption_state =
149 608 : cli_credentials_get_smb_encryption(creds);
150 :
151 1310 : if (encryption_state >= SMB_ENCRYPTION_DESIRED) {
152 6 : signing_state = SMB_SIGNING_REQUIRED;
153 : }
154 :
155 : /* make a copy so we don't modify the global string 'service' */
156 1310 : servicename = talloc_strdup(ctx,share);
157 1310 : if (!servicename) {
158 0 : return NT_STATUS_NO_MEMORY;
159 : }
160 1310 : sharename = servicename;
161 1310 : if (*sharename == '\\') {
162 1108 : sharename += 2;
163 1108 : if (server == NULL) {
164 1108 : server = sharename;
165 : }
166 1108 : sharename = strchr_m(sharename,'\\');
167 1108 : if (!sharename) {
168 0 : return NT_STATUS_NO_MEMORY;
169 : }
170 1108 : *sharename = 0;
171 1108 : sharename++;
172 : }
173 1310 : if (server == NULL) {
174 0 : return NT_STATUS_INVALID_PARAMETER;
175 : }
176 :
177 1310 : status = cli_connect_nb(
178 : server, dest_ss, port, name_type, NULL,
179 : signing_state,
180 : flags, &c);
181 :
182 1310 : if (!NT_STATUS_IS_OK(status)) {
183 0 : if (NT_STATUS_EQUAL(status, NT_STATUS_NOT_SUPPORTED)) {
184 0 : DBG_ERR("NetBIOS support disabled, unable to connect");
185 : }
186 :
187 0 : DBG_WARNING("Connection to %s failed (Error %s)\n",
188 : server,
189 : nt_errstr(status));
190 0 : return status;
191 : }
192 :
193 1310 : DEBUG(4,(" session request ok\n"));
194 :
195 1310 : status = smbXcli_negprot(c->conn, c->timeout,
196 1310 : lp_client_min_protocol(),
197 1310 : lp_client_max_protocol());
198 :
199 1310 : if (!NT_STATUS_IS_OK(status)) {
200 172 : d_printf("protocol negotiation failed: %s\n",
201 : nt_errstr(status));
202 172 : cli_shutdown(c);
203 172 : return status;
204 : }
205 1138 : protocol = smbXcli_conn_protocol(c->conn);
206 1138 : DEBUG(4,(" negotiated dialect[%s] against server[%s]\n",
207 : smb_protocol_types_string(protocol),
208 : smbXcli_conn_remote_name(c->conn)));
209 :
210 1138 : if (protocol >= PROTOCOL_SMB2_02) {
211 : /* Ensure we ask for some initial credits. */
212 1120 : smb2cli_conn_set_max_credits(c->conn, DEFAULT_SMB2_MAX_CREDITS);
213 : }
214 :
215 1138 : status = cli_session_setup_creds(c, creds);
216 1138 : if (!NT_STATUS_IS_OK(status)) {
217 : /* If a password was not supplied then
218 : * try again with a null username. */
219 260 : if (encryption_state == SMB_ENCRYPTION_REQUIRED ||
220 256 : smbXcli_conn_signing_mandatory(c->conn) ||
221 146 : cli_credentials_authentication_requested(creds) ||
222 32 : cli_credentials_is_anonymous(creds) ||
223 16 : !NT_STATUS_IS_OK(status = cli_session_setup_anon(c)))
224 : {
225 120 : d_printf("session setup failed: %s\n",
226 : nt_errstr(status));
227 120 : if (NT_STATUS_EQUAL(status,
228 : NT_STATUS_MORE_PROCESSING_REQUIRED))
229 0 : d_printf("did you forget to run kinit?\n");
230 120 : cli_shutdown(c);
231 120 : return status;
232 : }
233 10 : d_printf("Anonymous login successful\n");
234 : }
235 :
236 1018 : if (!NT_STATUS_IS_OK(status)) {
237 0 : DEBUG(10,("cli_init_creds() failed: %s\n", nt_errstr(status)));
238 0 : cli_shutdown(c);
239 0 : return status;
240 : }
241 :
242 1018 : DEBUG(4,(" session setup ok\n"));
243 :
244 1018 : if (encryption_state >= SMB_ENCRYPTION_DESIRED) {
245 6 : status = cli_cm_force_encryption_creds(c,
246 : creds,
247 : sharename);
248 6 : if (!NT_STATUS_IS_OK(status)) {
249 0 : switch (encryption_state) {
250 0 : case SMB_ENCRYPTION_DESIRED:
251 0 : break;
252 0 : case SMB_ENCRYPTION_REQUIRED:
253 : default:
254 0 : cli_shutdown(c);
255 0 : return status;
256 : }
257 552 : }
258 : }
259 :
260 : /* here's the fun part....to support 'msdfs proxy' shares
261 : (on Samba or windows) we have to issues a TRANS_GET_DFS_REFERRAL
262 : here before trying to connect to the original share.
263 : cli_check_msdfs_proxy() will fail if it is a normal share. */
264 :
265 1927 : if (smbXcli_conn_dfs_supported(c->conn) &&
266 909 : cli_check_msdfs_proxy(ctx, c, sharename,
267 : &newserver, &newshare,
268 : creds)) {
269 0 : cli_shutdown(c);
270 0 : return do_connect(ctx, newserver,
271 : newshare, creds,
272 : NULL, port, name_type, pcli);
273 : }
274 :
275 : /* must be a normal share */
276 :
277 1018 : status = cli_tree_connect_creds(c, sharename, "?????", creds);
278 1018 : if (!NT_STATUS_IS_OK(status)) {
279 14 : d_printf("tree connect failed: %s\n", nt_errstr(status));
280 14 : cli_shutdown(c);
281 14 : return status;
282 : }
283 :
284 1004 : DEBUG(4,(" tconx ok\n"));
285 1004 : *pcli = c;
286 1004 : return NT_STATUS_OK;
287 : }
288 :
289 : /********************************************************************
290 : Add a new connection to the list.
291 : referring_cli == NULL means a new initial connection.
292 : ********************************************************************/
293 :
294 1310 : static NTSTATUS cli_cm_connect(TALLOC_CTX *ctx,
295 : struct cli_state *referring_cli,
296 : const char *server,
297 : const char *share,
298 : struct cli_credentials *creds,
299 : const struct sockaddr_storage *dest_ss,
300 : int port,
301 : int name_type,
302 : struct cli_state **pcli)
303 : {
304 1310 : struct cli_state *cli = NULL;
305 : NTSTATUS status;
306 :
307 1310 : status = do_connect(ctx, server, share,
308 : creds,
309 : dest_ss, port, name_type, &cli);
310 :
311 1310 : if (!NT_STATUS_IS_OK(status)) {
312 306 : return status;
313 : }
314 :
315 : /*
316 : * This can't happen, this test is to satisfy static
317 : * checkers (clang)
318 : */
319 1004 : if (cli == NULL) {
320 0 : return NT_STATUS_NO_MEMORY;
321 : }
322 :
323 : /* Enter into the list. */
324 1004 : if (referring_cli) {
325 76 : DLIST_ADD_END(referring_cli, cli);
326 : }
327 :
328 1004 : if (referring_cli && referring_cli->requested_posix_capabilities) {
329 : uint16_t major, minor;
330 : uint32_t caplow, caphigh;
331 0 : status = cli_unix_extensions_version(cli, &major, &minor,
332 : &caplow, &caphigh);
333 0 : if (NT_STATUS_IS_OK(status)) {
334 0 : cli_set_unix_extensions_capabilities(cli,
335 : major, minor,
336 : caplow, caphigh);
337 : }
338 : }
339 :
340 1004 : *pcli = cli;
341 1004 : return NT_STATUS_OK;
342 : }
343 :
344 : /********************************************************************
345 : Return a connection to a server on a particular share.
346 : ********************************************************************/
347 :
348 3115 : static struct cli_state *cli_cm_find(struct cli_state *cli,
349 : const char *server,
350 : const char *share)
351 : {
352 : struct cli_state *p;
353 :
354 3115 : if (cli == NULL) {
355 1234 : return NULL;
356 : }
357 :
358 : /* Search to the start of the list. */
359 8102 : for (p = cli; p; p = DLIST_PREV(p)) {
360 3902 : const char *remote_name =
361 7793 : smbXcli_conn_remote_name(p->conn);
362 :
363 11131 : if (strequal(server, remote_name) &&
364 3338 : strequal(share,p->share)) {
365 1572 : return p;
366 : }
367 : }
368 :
369 : /* Search to the end of the list. */
370 357 : for (p = cli->next; p; p = p->next) {
371 24 : const char *remote_name =
372 48 : smbXcli_conn_remote_name(p->conn);
373 :
374 54 : if (strequal(server, remote_name) &&
375 6 : strequal(share,p->share)) {
376 0 : return p;
377 : }
378 : }
379 :
380 309 : return NULL;
381 : }
382 :
383 : /****************************************************************************
384 : Open a client connection to a \\server\share.
385 : ****************************************************************************/
386 :
387 2058 : NTSTATUS cli_cm_open(TALLOC_CTX *ctx,
388 : struct cli_state *referring_cli,
389 : const char *server,
390 : const char *share,
391 : struct cli_credentials *creds,
392 : const struct sockaddr_storage *dest_ss,
393 : int port,
394 : int name_type,
395 : struct cli_state **pcli)
396 : {
397 : /* Try to reuse an existing connection in this list. */
398 2058 : struct cli_state *c = cli_cm_find(referring_cli, server, share);
399 : NTSTATUS status;
400 :
401 2058 : if (c) {
402 792 : *pcli = c;
403 792 : return NT_STATUS_OK;
404 : }
405 :
406 1266 : if (creds == NULL) {
407 : /* Can't do a new connection
408 : * without auth info. */
409 0 : d_printf("cli_cm_open() Unable to open connection [\\%s\\%s] "
410 : "without client credentials\n",
411 : server, share );
412 0 : return NT_STATUS_INVALID_PARAMETER;
413 : }
414 :
415 1266 : status = cli_cm_connect(ctx,
416 : referring_cli,
417 : server,
418 : share,
419 : creds,
420 : dest_ss,
421 : port,
422 : name_type,
423 : &c);
424 1266 : if (!NT_STATUS_IS_OK(status)) {
425 306 : return status;
426 : }
427 960 : *pcli = c;
428 960 : return NT_STATUS_OK;
429 : }
430 :
431 : /****************************************************************************
432 : ****************************************************************************/
433 :
434 0 : void cli_cm_display(struct cli_state *cli)
435 : {
436 : int i;
437 :
438 0 : for (i=0; cli; cli = cli->next,i++ ) {
439 0 : d_printf("%d:\tserver=%s, share=%s\n",
440 : i, smbXcli_conn_remote_name(cli->conn), cli->share);
441 : }
442 0 : }
443 :
444 : /****************************************************************************
445 : ****************************************************************************/
446 :
447 : /****************************************************************************
448 : ****************************************************************************/
449 :
450 : #if 0
451 : void cli_cm_set_credentials(struct user_auth_info *auth_info)
452 : {
453 : SAFE_FREE(cm_creds.username);
454 : cm_creds.username = SMB_STRDUP(get_cmdline_auth_info_username(
455 : auth_info));
456 :
457 : if (get_cmdline_auth_info_got_pass(auth_info)) {
458 : cm_set_password(get_cmdline_auth_info_password(auth_info));
459 : }
460 :
461 : cm_creds.use_kerberos = get_cmdline_auth_info_use_kerberos(auth_info);
462 : cm_creds.fallback_after_kerberos = false;
463 : cm_creds.signing_state = get_cmdline_auth_info_signing_state(auth_info);
464 : }
465 : #endif
466 :
467 : /**********************************************************************
468 : split a dfs path into the server, share name, and extrapath components
469 : **********************************************************************/
470 :
471 1101 : static bool split_dfs_path(TALLOC_CTX *ctx,
472 : const char *nodepath,
473 : char **pp_server,
474 : char **pp_share,
475 : char **pp_extrapath)
476 : {
477 : char *p, *q;
478 : char *path;
479 :
480 1101 : *pp_server = NULL;
481 1101 : *pp_share = NULL;
482 1101 : *pp_extrapath = NULL;
483 :
484 1101 : path = talloc_strdup(ctx, nodepath);
485 1101 : if (!path) {
486 0 : goto fail;
487 : }
488 :
489 1101 : if ( path[0] != '\\' ) {
490 0 : goto fail;
491 : }
492 :
493 1101 : p = strchr_m( path + 1, '\\' );
494 1101 : if ( !p ) {
495 0 : goto fail;
496 : }
497 :
498 1101 : *p = '\0';
499 1101 : p++;
500 :
501 : /* Look for any extra/deep path */
502 1101 : q = strchr_m(p, '\\');
503 1101 : if (q != NULL) {
504 0 : *q = '\0';
505 0 : q++;
506 0 : *pp_extrapath = talloc_strdup(ctx, q);
507 : } else {
508 1101 : *pp_extrapath = talloc_strdup(ctx, "");
509 : }
510 1101 : if (*pp_extrapath == NULL) {
511 0 : goto fail;
512 : }
513 :
514 1101 : *pp_share = talloc_strdup(ctx, p);
515 1101 : if (*pp_share == NULL) {
516 0 : goto fail;
517 : }
518 :
519 1101 : *pp_server = talloc_strdup(ctx, &path[1]);
520 1101 : if (*pp_server == NULL) {
521 0 : goto fail;
522 : }
523 :
524 1101 : TALLOC_FREE(path);
525 1101 : return true;
526 :
527 0 : fail:
528 0 : TALLOC_FREE(*pp_share);
529 0 : TALLOC_FREE(*pp_extrapath);
530 0 : TALLOC_FREE(path);
531 0 : return false;
532 : }
533 :
534 : /****************************************************************************
535 : Return the original path truncated at the directory component before
536 : the first wildcard character. Trust the caller to provide a NULL
537 : terminated string
538 : ****************************************************************************/
539 :
540 912 : static char *clean_path(TALLOC_CTX *ctx, const char *path)
541 : {
542 : size_t len;
543 : char *p1, *p2, *p;
544 : char *path_out;
545 :
546 : /* No absolute paths. */
547 2282 : while (IS_DIRECTORY_SEP(*path)) {
548 912 : path++;
549 : }
550 :
551 912 : path_out = talloc_strdup(ctx, path);
552 912 : if (!path_out) {
553 0 : return NULL;
554 : }
555 :
556 912 : p1 = strchr_m(path_out, '*');
557 912 : p2 = strchr_m(path_out, '?');
558 :
559 912 : if (p1 || p2) {
560 712 : if (p1 && p2) {
561 0 : p = MIN(p1,p2);
562 712 : } else if (!p1) {
563 0 : p = p2;
564 : } else {
565 712 : p = p1;
566 : }
567 712 : *p = '\0';
568 :
569 : /* Now go back to the start of this component. */
570 712 : p1 = strrchr_m(path_out, '/');
571 712 : p2 = strrchr_m(path_out, '\\');
572 712 : p = MAX(p1,p2);
573 712 : if (p) {
574 692 : *p = '\0';
575 : }
576 : }
577 :
578 : /* Strip any trailing separator */
579 :
580 912 : len = strlen(path_out);
581 912 : if ( (len > 0) && IS_DIRECTORY_SEP(path_out[len-1])) {
582 56 : path_out[len-1] = '\0';
583 : }
584 :
585 912 : return path_out;
586 : }
587 :
588 : /****************************************************************************
589 : ****************************************************************************/
590 :
591 1844 : static char *cli_dfs_make_full_path(TALLOC_CTX *ctx,
592 : struct cli_state *cli,
593 : const char *dir)
594 : {
595 1844 : char path_sep = '\\';
596 :
597 : /* Ensure the extrapath doesn't start with a separator. */
598 3690 : while (IS_DIRECTORY_SEP(*dir)) {
599 920 : dir++;
600 : }
601 :
602 1844 : if (cli->requested_posix_capabilities & CIFS_UNIX_POSIX_PATHNAMES_CAP) {
603 0 : path_sep = '/';
604 : }
605 1844 : return talloc_asprintf(ctx, "%c%s%c%s%c%s",
606 : path_sep,
607 : smbXcli_conn_remote_name(cli->conn),
608 : path_sep,
609 : cli->share,
610 : path_sep,
611 : dir);
612 : }
613 :
614 : /********************************************************************
615 : Get the dfs referral link.
616 : ********************************************************************/
617 :
618 1651 : NTSTATUS cli_dfs_get_referral_ex(TALLOC_CTX *ctx,
619 : struct cli_state *cli,
620 : const char *path,
621 : uint16_t max_referral_level,
622 : struct client_dfs_referral **refs,
623 : size_t *num_refs,
624 : size_t *consumed)
625 : {
626 1651 : unsigned int param_len = 0;
627 : uint16_t recv_flags2;
628 1651 : uint8_t *param = NULL;
629 1651 : uint8_t *rdata = NULL;
630 : char *p;
631 : char *endp;
632 : smb_ucs2_t *path_ucs;
633 1651 : char *consumed_path = NULL;
634 : uint16_t consumed_ucs;
635 : uint16_t num_referrals;
636 1651 : struct client_dfs_referral *referrals = NULL;
637 : NTSTATUS status;
638 1651 : TALLOC_CTX *frame = talloc_stackframe();
639 :
640 1651 : *num_refs = 0;
641 1651 : *refs = NULL;
642 :
643 1651 : param = talloc_array(talloc_tos(), uint8_t, 2);
644 1651 : if (!param) {
645 0 : status = NT_STATUS_NO_MEMORY;
646 0 : goto out;
647 : }
648 1651 : SSVAL(param, 0, max_referral_level);
649 :
650 1651 : param = trans2_bytes_push_str(param, smbXcli_conn_use_unicode(cli->conn),
651 1651 : path, strlen(path)+1,
652 : NULL);
653 1651 : if (!param) {
654 0 : status = NT_STATUS_NO_MEMORY;
655 0 : goto out;
656 : }
657 1651 : param_len = talloc_get_size(param);
658 1651 : path_ucs = (smb_ucs2_t *)¶m[2];
659 :
660 1651 : if (smbXcli_conn_protocol(cli->conn) >= PROTOCOL_SMB2_02) {
661 : DATA_BLOB in_input_buffer;
662 1651 : DATA_BLOB in_output_buffer = data_blob_null;
663 1651 : DATA_BLOB out_input_buffer = data_blob_null;
664 1651 : DATA_BLOB out_output_buffer = data_blob_null;
665 :
666 1651 : in_input_buffer.data = param;
667 1651 : in_input_buffer.length = param_len;
668 :
669 2505 : status = smb2cli_ioctl(cli->conn,
670 1651 : cli->timeout,
671 : cli->smb2.session,
672 : cli->smb2.tcon,
673 : UINT64_MAX, /* in_fid_persistent */
674 : UINT64_MAX, /* in_fid_volatile */
675 : FSCTL_DFS_GET_REFERRALS,
676 : 0, /* in_max_input_length */
677 : &in_input_buffer,
678 : CLI_BUFFER_SIZE, /* in_max_output_length */
679 : &in_output_buffer,
680 : SMB2_IOCTL_FLAG_IS_FSCTL,
681 : talloc_tos(),
682 : &out_input_buffer,
683 : &out_output_buffer);
684 1651 : if (!NT_STATUS_IS_OK(status)) {
685 1201 : goto out;
686 : }
687 :
688 868 : if (out_output_buffer.length < 4) {
689 0 : status = NT_STATUS_INVALID_NETWORK_RESPONSE;
690 0 : goto out;
691 : }
692 :
693 868 : recv_flags2 = FLAGS2_UNICODE_STRINGS;
694 868 : rdata = out_output_buffer.data;
695 868 : endp = (char *)rdata + out_output_buffer.length;
696 : } else {
697 0 : unsigned int data_len = 0;
698 : uint16_t setup[1];
699 :
700 0 : SSVAL(setup, 0, TRANSACT2_GET_DFS_REFERRAL);
701 :
702 0 : status = cli_trans(talloc_tos(), cli, SMBtrans2,
703 : NULL, 0xffff, 0, 0,
704 : setup, 1, 0,
705 : param, param_len, 2,
706 : NULL, 0, CLI_BUFFER_SIZE,
707 : &recv_flags2,
708 : NULL, 0, NULL, /* rsetup */
709 : NULL, 0, NULL,
710 : &rdata, 4, &data_len);
711 0 : if (!NT_STATUS_IS_OK(status)) {
712 0 : goto out;
713 : }
714 :
715 0 : endp = (char *)rdata + data_len;
716 : }
717 :
718 868 : consumed_ucs = SVAL(rdata, 0);
719 868 : num_referrals = SVAL(rdata, 2);
720 :
721 : /* consumed_ucs is the number of bytes
722 : * of the UCS2 path consumed not counting any
723 : * terminating null. We need to convert
724 : * back to unix charset and count again
725 : * to get the number of bytes consumed from
726 : * the incoming path. */
727 :
728 868 : errno = 0;
729 868 : if (pull_string_talloc(talloc_tos(),
730 : NULL,
731 : 0,
732 : &consumed_path,
733 : path_ucs,
734 : consumed_ucs,
735 : STR_UNICODE) == 0) {
736 0 : if (errno != 0) {
737 0 : status = map_nt_error_from_unix(errno);
738 : } else {
739 0 : status = NT_STATUS_INVALID_NETWORK_RESPONSE;
740 : }
741 0 : goto out;
742 : }
743 868 : if (consumed_path == NULL) {
744 0 : status = map_nt_error_from_unix(errno);
745 0 : goto out;
746 : }
747 868 : *consumed = strlen(consumed_path);
748 :
749 868 : if (num_referrals != 0) {
750 : uint16_t ref_version;
751 : uint16_t ref_size;
752 : int i;
753 : uint16_t node_offset;
754 :
755 868 : referrals = talloc_array(ctx, struct client_dfs_referral,
756 : num_referrals);
757 :
758 868 : if (!referrals) {
759 0 : status = NT_STATUS_NO_MEMORY;
760 0 : goto out;
761 : }
762 : /* start at the referrals array */
763 :
764 868 : p = (char *)rdata+8;
765 2544 : for (i=0; i<num_referrals && p < endp; i++) {
766 1676 : if (p + 18 > endp) {
767 0 : goto out;
768 : }
769 1676 : ref_version = SVAL(p, 0);
770 1676 : ref_size = SVAL(p, 2);
771 1676 : node_offset = SVAL(p, 16);
772 :
773 1676 : if (ref_version != 3) {
774 0 : p += ref_size;
775 0 : continue;
776 : }
777 :
778 1676 : referrals[i].proximity = SVAL(p, 8);
779 1676 : referrals[i].ttl = SVAL(p, 10);
780 :
781 1676 : if (p + node_offset > endp) {
782 0 : status = NT_STATUS_INVALID_NETWORK_RESPONSE;
783 0 : goto out;
784 : }
785 2518 : pull_string_talloc(referrals,
786 : (const char *)rdata,
787 : recv_flags2,
788 1676 : &referrals[i].dfspath,
789 1676 : p+node_offset,
790 1676 : PTR_DIFF(endp, p+node_offset),
791 : STR_TERMINATE|STR_UNICODE);
792 :
793 1676 : if (!referrals[i].dfspath) {
794 0 : status = map_nt_error_from_unix(errno);
795 0 : goto out;
796 : }
797 1676 : p += ref_size;
798 : }
799 868 : if (i < num_referrals) {
800 0 : status = NT_STATUS_INVALID_NETWORK_RESPONSE;
801 0 : goto out;
802 : }
803 : }
804 :
805 868 : *num_refs = num_referrals;
806 868 : *refs = referrals;
807 :
808 1651 : out:
809 :
810 1651 : TALLOC_FREE(frame);
811 1651 : return status;
812 : }
813 :
814 1651 : NTSTATUS cli_dfs_get_referral(TALLOC_CTX *ctx,
815 : struct cli_state *cli,
816 : const char *path,
817 : struct client_dfs_referral **refs,
818 : size_t *num_refs,
819 : size_t *consumed)
820 : {
821 1651 : return cli_dfs_get_referral_ex(ctx,
822 : cli,
823 : path,
824 : 3,
825 : refs, /* Max referral level we want */
826 : num_refs,
827 : consumed);
828 : }
829 :
830 2628 : static bool cli_conn_have_dfs(struct cli_state *cli)
831 : {
832 2628 : struct smbXcli_conn *conn = cli->conn;
833 2628 : struct smbXcli_tcon *tcon = NULL;
834 : bool ok;
835 :
836 2628 : if (smbXcli_conn_protocol(conn) < PROTOCOL_SMB2_02) {
837 0 : uint32_t capabilities = smb1cli_conn_capabilities(conn);
838 :
839 0 : if ((capabilities & CAP_STATUS32) == 0) {
840 0 : return false;
841 : }
842 0 : if ((capabilities & CAP_UNICODE) == 0) {
843 0 : return false;
844 : }
845 :
846 0 : tcon = cli->smb1.tcon;
847 : } else {
848 2628 : tcon = cli->smb2.tcon;
849 : }
850 :
851 2628 : ok = smbXcli_tcon_is_dfs_share(tcon);
852 2628 : return ok;
853 : }
854 :
855 : /********************************************************************
856 : ********************************************************************/
857 : struct cli_dfs_path_split {
858 : char *server;
859 : char *share;
860 : char *extrapath;
861 : };
862 :
863 2628 : NTSTATUS cli_resolve_path(TALLOC_CTX *ctx,
864 : const char *mountpt,
865 : struct cli_credentials *creds,
866 : struct cli_state *rootcli,
867 : const char *path,
868 : struct cli_state **targetcli,
869 : char **pp_targetpath)
870 : {
871 2628 : struct client_dfs_referral *refs = NULL;
872 2628 : size_t num_refs = 0;
873 2628 : size_t consumed = 0;
874 2628 : struct cli_state *cli_ipc = NULL;
875 2628 : char *dfs_path = NULL;
876 2628 : char *cleanpath = NULL;
877 2628 : char *extrapath = NULL;
878 : int pathlen;
879 2628 : struct cli_state *newcli = NULL;
880 2628 : struct cli_state *ccli = NULL;
881 2628 : size_t count = 0;
882 2628 : char *newpath = NULL;
883 2628 : char *newmount = NULL;
884 2628 : char *ppath = NULL;
885 : SMB_STRUCT_STAT sbuf;
886 : uint32_t attributes;
887 : NTSTATUS status;
888 2628 : struct smbXcli_tcon *target_tcon = NULL;
889 2628 : struct cli_dfs_path_split *dfs_refs = NULL;
890 : bool ok;
891 :
892 2628 : if ( !rootcli || !path || !targetcli ) {
893 0 : return NT_STATUS_INVALID_PARAMETER;
894 : }
895 :
896 : /*
897 : * Avoid more than one leading directory separator
898 : */
899 3989 : while (IS_DIRECTORY_SEP(path[0]) && IS_DIRECTORY_SEP(path[1])) {
900 0 : path++;
901 : }
902 :
903 2628 : ok = cli_conn_have_dfs(rootcli);
904 2628 : if (!ok) {
905 1716 : *targetcli = rootcli;
906 1716 : *pp_targetpath = talloc_strdup(ctx, path);
907 1716 : if (!*pp_targetpath) {
908 0 : return NT_STATUS_NO_MEMORY;
909 : }
910 1716 : return NT_STATUS_OK;
911 : }
912 :
913 912 : *targetcli = NULL;
914 :
915 : /* Send a trans2_query_path_info to check for a referral. */
916 :
917 912 : cleanpath = clean_path(ctx, path);
918 912 : if (!cleanpath) {
919 0 : return NT_STATUS_NO_MEMORY;
920 : }
921 :
922 912 : dfs_path = cli_dfs_make_full_path(ctx, rootcli, cleanpath);
923 912 : if (!dfs_path) {
924 0 : return NT_STATUS_NO_MEMORY;
925 : }
926 :
927 912 : status = cli_qpathinfo_basic( rootcli, dfs_path, &sbuf, &attributes);
928 912 : if (NT_STATUS_IS_OK(status)) {
929 : /* This is an ordinary path, just return it. */
930 72 : *targetcli = rootcli;
931 72 : *pp_targetpath = talloc_strdup(ctx, path);
932 72 : if (!*pp_targetpath) {
933 0 : return NT_STATUS_NO_MEMORY;
934 : }
935 72 : goto done;
936 : }
937 :
938 : /* Special case where client asked for a path that does not exist */
939 :
940 840 : if (NT_STATUS_EQUAL(status, NT_STATUS_OBJECT_NAME_NOT_FOUND)) {
941 16 : *targetcli = rootcli;
942 16 : *pp_targetpath = talloc_strdup(ctx, path);
943 16 : if (!*pp_targetpath) {
944 0 : return NT_STATUS_NO_MEMORY;
945 : }
946 16 : goto done;
947 : }
948 :
949 : /* We got an error, check for DFS referral. */
950 :
951 824 : if (!NT_STATUS_EQUAL(status, NT_STATUS_PATH_NOT_COVERED)) {
952 0 : return status;
953 : }
954 :
955 : /* Check for the referral. */
956 :
957 824 : status = cli_cm_open(ctx,
958 : rootcli,
959 : smbXcli_conn_remote_name(rootcli->conn),
960 : "IPC$",
961 : creds,
962 : NULL, /* dest_ss not needed, we reuse the transport */
963 : 0,
964 : 0x20,
965 : &cli_ipc);
966 824 : if (!NT_STATUS_IS_OK(status)) {
967 0 : return status;
968 : }
969 :
970 824 : status = cli_dfs_get_referral(ctx, cli_ipc, dfs_path, &refs,
971 : &num_refs, &consumed);
972 824 : if (!NT_STATUS_IS_OK(status)) {
973 0 : return status;
974 : }
975 :
976 824 : if (!num_refs || !refs[0].dfspath) {
977 0 : return NT_STATUS_NOT_FOUND;
978 : }
979 :
980 : /*
981 : * Bug#10123 - DFS referal entries can be provided in a random order,
982 : * so check the connection cache for each item to avoid unnecessary
983 : * reconnections.
984 : */
985 824 : dfs_refs = talloc_array(ctx, struct cli_dfs_path_split, num_refs);
986 824 : if (dfs_refs == NULL) {
987 0 : return NT_STATUS_NO_MEMORY;
988 : }
989 :
990 1101 : for (count = 0; count < num_refs; count++) {
991 2113 : if (!split_dfs_path(dfs_refs, refs[count].dfspath,
992 1057 : &dfs_refs[count].server,
993 1057 : &dfs_refs[count].share,
994 1057 : &dfs_refs[count].extrapath)) {
995 0 : TALLOC_FREE(dfs_refs);
996 0 : return NT_STATUS_NOT_FOUND;
997 : }
998 :
999 1057 : ccli = cli_cm_find(rootcli, dfs_refs[count].server,
1000 1057 : dfs_refs[count].share);
1001 1057 : if (ccli != NULL) {
1002 780 : extrapath = dfs_refs[count].extrapath;
1003 780 : *targetcli = ccli;
1004 780 : break;
1005 : }
1006 : }
1007 :
1008 : /*
1009 : * If no cached connection was found, then connect to the first live
1010 : * referral server in the list.
1011 : */
1012 1238 : for (count = 0; (ccli == NULL) && (count < num_refs); count++) {
1013 : /* Connect to the target server & share */
1014 44 : status = cli_cm_connect(ctx, rootcli,
1015 44 : dfs_refs[count].server,
1016 44 : dfs_refs[count].share,
1017 : creds,
1018 : NULL, /* dest_ss */
1019 : 0, /* port */
1020 : 0x20,
1021 : targetcli);
1022 44 : if (!NT_STATUS_IS_OK(status)) {
1023 0 : d_printf("Unable to follow dfs referral [\\%s\\%s]\n",
1024 0 : dfs_refs[count].server,
1025 0 : dfs_refs[count].share);
1026 0 : continue;
1027 : } else {
1028 44 : extrapath = dfs_refs[count].extrapath;
1029 44 : break;
1030 : }
1031 : }
1032 :
1033 : /* No available referral server for the connection */
1034 824 : if (*targetcli == NULL) {
1035 0 : TALLOC_FREE(dfs_refs);
1036 0 : return status;
1037 : }
1038 :
1039 : /* Make sure to recreate the original string including any wildcards. */
1040 :
1041 824 : dfs_path = cli_dfs_make_full_path(ctx, rootcli, path);
1042 824 : if (!dfs_path) {
1043 0 : TALLOC_FREE(dfs_refs);
1044 0 : return NT_STATUS_NO_MEMORY;
1045 : }
1046 824 : pathlen = strlen(dfs_path);
1047 824 : consumed = MIN(pathlen, consumed);
1048 824 : *pp_targetpath = talloc_strdup(ctx, &dfs_path[consumed]);
1049 824 : if (!*pp_targetpath) {
1050 0 : TALLOC_FREE(dfs_refs);
1051 0 : return NT_STATUS_NO_MEMORY;
1052 : }
1053 824 : dfs_path[consumed] = '\0';
1054 :
1055 : /*
1056 : * *pp_targetpath is now the unconsumed part of the path.
1057 : * dfs_path is now the consumed part of the path
1058 : * (in \server\share\path format).
1059 : */
1060 :
1061 824 : if (extrapath && strlen(extrapath) > 0) {
1062 : /* EMC Celerra NAS version 5.6.50 (at least) doesn't appear to */
1063 : /* put the trailing \ on the path, so to be safe we put one in if needed */
1064 0 : if (extrapath[strlen(extrapath)-1] != '\\' && **pp_targetpath != '\\') {
1065 0 : *pp_targetpath = talloc_asprintf(ctx,
1066 : "%s\\%s",
1067 : extrapath,
1068 : *pp_targetpath);
1069 : } else {
1070 0 : *pp_targetpath = talloc_asprintf(ctx,
1071 : "%s%s",
1072 : extrapath,
1073 : *pp_targetpath);
1074 : }
1075 0 : if (!*pp_targetpath) {
1076 0 : TALLOC_FREE(dfs_refs);
1077 0 : return NT_STATUS_NO_MEMORY;
1078 : }
1079 : }
1080 :
1081 : /* parse out the consumed mount path */
1082 : /* trim off the \server\share\ */
1083 :
1084 824 : ppath = dfs_path;
1085 :
1086 824 : if (*ppath != '\\') {
1087 0 : d_printf("cli_resolve_path: "
1088 : "dfs_path (%s) not in correct format.\n",
1089 : dfs_path );
1090 0 : TALLOC_FREE(dfs_refs);
1091 0 : return NT_STATUS_NOT_FOUND;
1092 : }
1093 :
1094 824 : ppath++; /* Now pointing at start of server name. */
1095 :
1096 824 : if ((ppath = strchr_m( dfs_path, '\\' )) == NULL) {
1097 0 : TALLOC_FREE(dfs_refs);
1098 0 : return NT_STATUS_NOT_FOUND;
1099 : }
1100 :
1101 824 : ppath++; /* Now pointing at start of share name. */
1102 :
1103 824 : if ((ppath = strchr_m( ppath+1, '\\' )) == NULL) {
1104 0 : TALLOC_FREE(dfs_refs);
1105 0 : return NT_STATUS_NOT_FOUND;
1106 : }
1107 :
1108 824 : ppath++; /* Now pointing at path component. */
1109 :
1110 824 : newmount = talloc_asprintf(ctx, "%s\\%s", mountpt, ppath );
1111 824 : if (!newmount) {
1112 0 : TALLOC_FREE(dfs_refs);
1113 0 : return NT_STATUS_NOT_FOUND;
1114 : }
1115 :
1116 : /* Check for another dfs referral, note that we are not
1117 : checking for loops here. */
1118 :
1119 824 : if (!strequal(*pp_targetpath, "\\") && !strequal(*pp_targetpath, "/")) {
1120 788 : status = cli_resolve_path(ctx,
1121 : newmount,
1122 : creds,
1123 : *targetcli,
1124 : *pp_targetpath,
1125 : &newcli,
1126 : &newpath);
1127 788 : if (NT_STATUS_IS_OK(status)) {
1128 : /*
1129 : * When cli_resolve_path returns true here it's always
1130 : * returning the complete path in newpath, so we're done
1131 : * here.
1132 : */
1133 788 : *targetcli = newcli;
1134 788 : *pp_targetpath = newpath;
1135 788 : TALLOC_FREE(dfs_refs);
1136 788 : return status;
1137 : }
1138 : }
1139 :
1140 36 : done:
1141 :
1142 124 : if (smbXcli_conn_protocol((*targetcli)->conn) >= PROTOCOL_SMB2_02) {
1143 124 : target_tcon = (*targetcli)->smb2.tcon;
1144 : } else {
1145 0 : target_tcon = (*targetcli)->smb1.tcon;
1146 : }
1147 :
1148 : /* If returning true ensure we return a dfs root full path. */
1149 124 : if (smbXcli_tcon_is_dfs_share(target_tcon)) {
1150 96 : dfs_path = talloc_strdup(ctx, *pp_targetpath);
1151 96 : if (!dfs_path) {
1152 0 : TALLOC_FREE(dfs_refs);
1153 0 : return NT_STATUS_NO_MEMORY;
1154 : }
1155 96 : *pp_targetpath = cli_dfs_make_full_path(ctx, *targetcli, dfs_path);
1156 96 : if (*pp_targetpath == NULL) {
1157 0 : TALLOC_FREE(dfs_refs);
1158 0 : return NT_STATUS_NO_MEMORY;
1159 : }
1160 : }
1161 :
1162 124 : TALLOC_FREE(dfs_refs);
1163 124 : return NT_STATUS_OK;
1164 : }
1165 :
1166 : /********************************************************************
1167 : ********************************************************************/
1168 :
1169 909 : bool cli_check_msdfs_proxy(TALLOC_CTX *ctx,
1170 : struct cli_state *cli,
1171 : const char *sharename,
1172 : char **pp_newserver,
1173 : char **pp_newshare,
1174 : struct cli_credentials *creds)
1175 : {
1176 909 : struct client_dfs_referral *refs = NULL;
1177 909 : size_t num_refs = 0;
1178 909 : size_t consumed = 0;
1179 909 : char *fullpath = NULL;
1180 : bool res;
1181 909 : struct smbXcli_tcon *orig_tcon = NULL;
1182 909 : char *newextrapath = NULL;
1183 : NTSTATUS status;
1184 : const char *remote_name;
1185 487 : enum smb_encryption_setting encryption_state =
1186 422 : cli_credentials_get_smb_encryption(creds);
1187 :
1188 909 : if (!cli || !sharename) {
1189 0 : return false;
1190 : }
1191 :
1192 909 : remote_name = smbXcli_conn_remote_name(cli->conn);
1193 :
1194 : /* special case. never check for a referral on the IPC$ share */
1195 :
1196 909 : if (strequal(sharename, "IPC$")) {
1197 82 : return false;
1198 : }
1199 :
1200 : /* send a trans2_query_path_info to check for a referral */
1201 :
1202 827 : fullpath = talloc_asprintf(ctx, "\\%s\\%s", remote_name, sharename);
1203 827 : if (!fullpath) {
1204 0 : return false;
1205 : }
1206 :
1207 : /* Store tcon state. */
1208 827 : if (cli_state_has_tcon(cli)) {
1209 0 : orig_tcon = cli_state_save_tcon(cli);
1210 0 : if (orig_tcon == NULL) {
1211 0 : return false;
1212 : }
1213 : }
1214 :
1215 : /* check for the referral */
1216 :
1217 827 : if (!NT_STATUS_IS_OK(cli_tree_connect(cli, "IPC$", "IPC", NULL))) {
1218 0 : cli_state_restore_tcon(cli, orig_tcon);
1219 0 : return false;
1220 : }
1221 :
1222 827 : if (encryption_state >= SMB_ENCRYPTION_DESIRED) {
1223 6 : status = cli_cm_force_encryption_creds(cli, creds, "IPC$");
1224 6 : if (!NT_STATUS_IS_OK(status)) {
1225 0 : switch (encryption_state) {
1226 0 : case SMB_ENCRYPTION_DESIRED:
1227 0 : break;
1228 0 : case SMB_ENCRYPTION_REQUIRED:
1229 : default:
1230 : /*
1231 : * Failed to set up encryption.
1232 : * Disconnect the temporary IPC$
1233 : * tcon before restoring the original
1234 : * tcon so we don't leak it.
1235 : */
1236 0 : cli_tdis(cli);
1237 0 : cli_state_restore_tcon(cli, orig_tcon);
1238 0 : return false;
1239 : }
1240 440 : }
1241 : }
1242 :
1243 827 : status = cli_dfs_get_referral(ctx, cli, fullpath, &refs,
1244 : &num_refs, &consumed);
1245 827 : res = NT_STATUS_IS_OK(status);
1246 :
1247 827 : status = cli_tdis(cli);
1248 :
1249 827 : cli_state_restore_tcon(cli, orig_tcon);
1250 :
1251 827 : if (!NT_STATUS_IS_OK(status)) {
1252 0 : return false;
1253 : }
1254 :
1255 827 : if (!res || !num_refs) {
1256 783 : return false;
1257 : }
1258 :
1259 44 : if (!refs[0].dfspath) {
1260 0 : return false;
1261 : }
1262 :
1263 44 : if (!split_dfs_path(ctx, refs[0].dfspath, pp_newserver,
1264 : pp_newshare, &newextrapath)) {
1265 0 : return false;
1266 : }
1267 :
1268 : /* check that this is not a self-referral */
1269 :
1270 88 : if (strequal(remote_name, *pp_newserver) &&
1271 44 : strequal(sharename, *pp_newshare)) {
1272 44 : return false;
1273 : }
1274 :
1275 0 : return true;
1276 : }
1277 :
1278 : /********************************************************************
1279 : Windows and NetApp (and arguably the SMB1/2/3 specs) expect a non-DFS
1280 : path for the targets of rename and hardlink. If we have been given
1281 : a DFS path for these calls, convert it back into a local path by
1282 : stripping off the DFS prefix.
1283 : ********************************************************************/
1284 :
1285 31 : NTSTATUS cli_dfs_target_check(TALLOC_CTX *mem_ctx,
1286 : struct cli_state *cli,
1287 : const char *fname_src,
1288 : const char *fname_dst,
1289 : const char **fname_dst_out)
1290 : {
1291 31 : char *dfs_prefix = NULL;
1292 31 : size_t prefix_len = 0;
1293 31 : struct smbXcli_tcon *tcon = NULL;
1294 :
1295 31 : if (!smbXcli_conn_dfs_supported(cli->conn)) {
1296 0 : goto copy_fname_out;
1297 : }
1298 31 : if (smbXcli_conn_protocol(cli->conn) >= PROTOCOL_SMB2_02) {
1299 25 : tcon = cli->smb2.tcon;
1300 : } else {
1301 6 : tcon = cli->smb1.tcon;
1302 : }
1303 31 : if (!smbXcli_tcon_is_dfs_share(tcon)) {
1304 19 : goto copy_fname_out;
1305 : }
1306 12 : dfs_prefix = cli_dfs_make_full_path(mem_ctx, cli, "");
1307 12 : if (dfs_prefix == NULL) {
1308 0 : return NT_STATUS_NO_MEMORY;
1309 : }
1310 12 : prefix_len = strlen(dfs_prefix);
1311 12 : if (strncmp(fname_dst, dfs_prefix, prefix_len) != 0) {
1312 : /*
1313 : * Prefix doesn't match. Assume it was
1314 : * already stripped or not added in the
1315 : * first place.
1316 : */
1317 4 : goto copy_fname_out;
1318 : }
1319 : /* Return the trailing name after the prefix. */
1320 8 : *fname_dst_out = &fname_dst[prefix_len];
1321 8 : TALLOC_FREE(dfs_prefix);
1322 8 : return NT_STATUS_OK;
1323 :
1324 23 : copy_fname_out:
1325 :
1326 : /*
1327 : * No change to the destination name. Just
1328 : * point it at the incoming destination name.
1329 : */
1330 23 : *fname_dst_out = fname_dst;
1331 23 : TALLOC_FREE(dfs_prefix);
1332 23 : return NT_STATUS_OK;
1333 : }
|