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