Merge tag 'ceph-for-6.4-rc1' of https://github.com/ceph/ceph-client
[linux-block.git] / fs / cifs / dfs.c
CommitLineData
abdb1742
PA
1// SPDX-License-Identifier: GPL-2.0
2/*
3 * Copyright (c) 2022 Paulo Alcantara <palcantara@suse.de>
4 */
5
a1c0d005 6#include <linux/namei.h>
abdb1742
PA
7#include "cifsproto.h"
8#include "cifs_debug.h"
9#include "dns_resolve.h"
10#include "fs_context.h"
11#include "dfs.h"
12
abdb1742
PA
13/**
14 * dfs_parse_target_referral - set fs context for dfs target referral
15 *
16 * @full_path: full path in UNC format.
17 * @ref: dfs referral pointer.
18 * @ctx: smb3 fs context pointer.
19 *
20 * Return zero if dfs referral was parsed correctly, otherwise non-zero.
21 */
22int dfs_parse_target_referral(const char *full_path, const struct dfs_info3_param *ref,
23 struct smb3_fs_context *ctx)
24{
25 int rc;
26 const char *prepath = NULL;
27 char *path;
28
29 if (!full_path || !*full_path || !ref || !ctx)
30 return -EINVAL;
31
32 if (WARN_ON_ONCE(!ref->node_name || ref->path_consumed < 0))
33 return -EINVAL;
34
35 if (strlen(full_path) - ref->path_consumed) {
36 prepath = full_path + ref->path_consumed;
37 /* skip initial delimiter */
38 if (*prepath == '/' || *prepath == '\\')
39 prepath++;
40 }
41
42 path = cifs_build_devname(ref->node_name, prepath);
43 if (IS_ERR(path))
44 return PTR_ERR(path);
45
46 rc = smb3_parse_devname(path, ctx);
47 if (rc)
48 goto out;
49
6d740164 50 rc = dns_resolve_server_name_to_ip(path, (struct sockaddr *)&ctx->dstaddr, NULL);
abdb1742
PA
51
52out:
53 kfree(path);
54 return rc;
55}
a1c0d005
PA
56
57/*
58 * cifs_build_path_to_root returns full path to root when we do not have an
59 * existing connection (tcon)
60 */
61static char *build_unc_path_to_root(const struct smb3_fs_context *ctx,
62 const struct cifs_sb_info *cifs_sb, bool useppath)
63{
64 char *full_path, *pos;
65 unsigned int pplen = useppath && ctx->prepath ? strlen(ctx->prepath) + 1 : 0;
66 unsigned int unc_len = strnlen(ctx->UNC, MAX_TREE_SIZE + 1);
67
68 if (unc_len > MAX_TREE_SIZE)
69 return ERR_PTR(-EINVAL);
70
71 full_path = kmalloc(unc_len + pplen + 1, GFP_KERNEL);
72 if (full_path == NULL)
73 return ERR_PTR(-ENOMEM);
74
75 memcpy(full_path, ctx->UNC, unc_len);
76 pos = full_path + unc_len;
77
78 if (pplen) {
79 *pos = CIFS_DIR_SEP(cifs_sb);
80 memcpy(pos + 1, ctx->prepath, pplen);
81 pos += pplen;
82 }
83
84 *pos = '\0'; /* add trailing null */
85 convert_delimiter(full_path, CIFS_DIR_SEP(cifs_sb));
86 cifs_dbg(FYI, "%s: full_path=%s\n", __func__, full_path);
87 return full_path;
88}
89
90static int get_session(struct cifs_mount_ctx *mnt_ctx, const char *full_path)
91{
92 struct smb3_fs_context *ctx = mnt_ctx->fs_ctx;
93 int rc;
94
95 ctx->leaf_fullpath = (char *)full_path;
96 rc = cifs_mount_get_session(mnt_ctx);
97 ctx->leaf_fullpath = NULL;
98
99 return rc;
100}
101
396935de 102static int get_root_smb_session(struct cifs_mount_ctx *mnt_ctx)
a1c0d005 103{
b56bce50 104 struct smb3_fs_context *ctx = mnt_ctx->fs_ctx;
396935de 105 struct dfs_root_ses *root_ses;
b56bce50
PA
106 struct cifs_ses *ses = mnt_ctx->ses;
107
108 if (ses) {
396935de
PA
109 root_ses = kmalloc(sizeof(*root_ses), GFP_KERNEL);
110 if (!root_ses)
111 return -ENOMEM;
112
113 INIT_LIST_HEAD(&root_ses->list);
114
a1c0d005 115 spin_lock(&cifs_tcp_ses_lock);
b56bce50 116 ses->ses_count++;
a1c0d005 117 spin_unlock(&cifs_tcp_ses_lock);
396935de
PA
118 root_ses->ses = ses;
119 list_add_tail(&root_ses->list, &mnt_ctx->dfs_ses_list);
a1c0d005 120 }
396935de
PA
121 ctx->dfs_root_ses = ses;
122 return 0;
a1c0d005
PA
123}
124
125static int get_dfs_conn(struct cifs_mount_ctx *mnt_ctx, const char *ref_path, const char *full_path,
126 const struct dfs_cache_tgt_iterator *tit)
127{
128 struct smb3_fs_context *ctx = mnt_ctx->fs_ctx;
129 struct dfs_info3_param ref = {};
396935de
PA
130 bool is_refsrv = false;
131 int rc, rc2;
a1c0d005
PA
132
133 rc = dfs_cache_get_tgt_referral(ref_path + 1, tit, &ref);
134 if (rc)
135 return rc;
136
137 rc = dfs_parse_target_referral(full_path + 1, &ref, ctx);
138 if (rc)
139 goto out;
140
141 cifs_mount_put_conns(mnt_ctx);
142 rc = get_session(mnt_ctx, ref_path);
143 if (rc)
144 goto out;
145
396935de 146 is_refsrv = !!(ref.flags & DFSREF_REFERRAL_SERVER);
a1c0d005
PA
147
148 rc = -EREMOTE;
149 if (ref.flags & DFSREF_STORAGE_SERVER) {
150 rc = cifs_mount_get_tcon(mnt_ctx);
151 if (rc)
152 goto out;
153
154 /* some servers may not advertise referral capability under ref.flags */
396935de 155 is_refsrv |= is_tcon_dfs(mnt_ctx->tcon);
a1c0d005
PA
156
157 rc = cifs_is_path_remote(mnt_ctx);
158 }
159
396935de
PA
160 if (rc == -EREMOTE && is_refsrv) {
161 rc2 = get_root_smb_session(mnt_ctx);
162 if (rc2)
163 rc = rc2;
164 }
165
a1c0d005
PA
166out:
167 free_dfs_info_param(&ref);
168 return rc;
169}
170
171static int __dfs_mount_share(struct cifs_mount_ctx *mnt_ctx)
172{
173 struct cifs_sb_info *cifs_sb = mnt_ctx->cifs_sb;
174 struct smb3_fs_context *ctx = mnt_ctx->fs_ctx;
175 char *ref_path = NULL, *full_path = NULL;
176 struct dfs_cache_tgt_iterator *tit;
177 struct TCP_Server_Info *server;
396935de 178 struct cifs_tcon *tcon;
a1c0d005
PA
179 char *origin_fullpath = NULL;
180 int num_links = 0;
181 int rc;
182
183 ref_path = dfs_get_path(cifs_sb, ctx->UNC);
184 if (IS_ERR(ref_path))
185 return PTR_ERR(ref_path);
186
187 full_path = build_unc_path_to_root(ctx, cifs_sb, true);
188 if (IS_ERR(full_path)) {
189 rc = PTR_ERR(full_path);
190 full_path = NULL;
191 goto out;
192 }
193
194 origin_fullpath = kstrdup(full_path, GFP_KERNEL);
195 if (!origin_fullpath) {
196 rc = -ENOMEM;
197 goto out;
198 }
199
200 do {
201 struct dfs_cache_tgt_list tl = DFS_CACHE_TGT_LIST_INIT(tl);
202
203 rc = dfs_get_referral(mnt_ctx, ref_path + 1, NULL, &tl);
204 if (rc)
205 break;
206
207 tit = dfs_cache_get_tgt_iterator(&tl);
208 if (!tit) {
209 cifs_dbg(VFS, "%s: dfs referral (%s) with no targets\n", __func__,
210 ref_path + 1);
211 rc = -ENOENT;
212 dfs_cache_free_tgts(&tl);
213 break;
214 }
215
216 do {
217 rc = get_dfs_conn(mnt_ctx, ref_path, full_path, tit);
218 if (!rc)
219 break;
220 if (rc == -EREMOTE) {
221 if (++num_links > MAX_NESTED_LINKS) {
222 rc = -ELOOP;
223 break;
224 }
225 kfree(ref_path);
226 kfree(full_path);
227 ref_path = full_path = NULL;
228
229 full_path = build_unc_path_to_root(ctx, cifs_sb, true);
230 if (IS_ERR(full_path)) {
231 rc = PTR_ERR(full_path);
232 full_path = NULL;
233 } else {
234 ref_path = dfs_get_path(cifs_sb, full_path);
235 if (IS_ERR(ref_path)) {
236 rc = PTR_ERR(ref_path);
237 ref_path = NULL;
238 }
239 }
240 break;
241 }
242 } while ((tit = dfs_cache_get_next_tgt(&tl, tit)));
243 dfs_cache_free_tgts(&tl);
244 } while (rc == -EREMOTE);
245
246 if (!rc) {
247 server = mnt_ctx->server;
396935de 248 tcon = mnt_ctx->tcon;
a1c0d005
PA
249
250 mutex_lock(&server->refpath_lock);
396935de
PA
251 if (!server->origin_fullpath) {
252 server->origin_fullpath = origin_fullpath;
253 server->current_fullpath = server->leaf_fullpath;
254 origin_fullpath = NULL;
255 }
a1c0d005 256 mutex_unlock(&server->refpath_lock);
396935de
PA
257
258 if (list_empty(&tcon->dfs_ses_list)) {
259 list_replace_init(&mnt_ctx->dfs_ses_list,
260 &tcon->dfs_ses_list);
261 } else {
262 dfs_put_root_smb_sessions(&mnt_ctx->dfs_ses_list);
263 }
a1c0d005
PA
264 }
265
266out:
267 kfree(origin_fullpath);
268 kfree(ref_path);
269 kfree(full_path);
270 return rc;
271}
272
273int dfs_mount_share(struct cifs_mount_ctx *mnt_ctx, bool *isdfs)
274{
275 struct cifs_sb_info *cifs_sb = mnt_ctx->cifs_sb;
276 struct smb3_fs_context *ctx = mnt_ctx->fs_ctx;
277 int rc;
278
279 *isdfs = false;
280
281 rc = get_session(mnt_ctx, NULL);
282 if (rc)
283 return rc;
b56bce50 284 ctx->dfs_root_ses = mnt_ctx->ses;
a1c0d005
PA
285 /*
286 * If called with 'nodfs' mount option, then skip DFS resolving. Otherwise unconditionally
287 * try to get an DFS referral (even cached) to determine whether it is an DFS mount.
288 *
289 * Skip prefix path to provide support for DFS referrals from w2k8 servers which don't seem
290 * to respond with PATH_NOT_COVERED to requests that include the prefix.
291 */
292 if ((cifs_sb->mnt_cifs_flags & CIFS_MOUNT_NO_DFS) ||
293 dfs_get_referral(mnt_ctx, ctx->UNC + 1, NULL, NULL)) {
294 rc = cifs_mount_get_tcon(mnt_ctx);
295 if (rc)
296 return rc;
297
298 rc = cifs_is_path_remote(mnt_ctx);
299 if (!rc || rc != -EREMOTE)
300 return rc;
301 }
302
303 *isdfs = true;
396935de
PA
304 rc = get_root_smb_session(mnt_ctx);
305 if (rc)
306 return rc;
a1c0d005
PA
307
308 return __dfs_mount_share(mnt_ctx);
309}
1d04a6fe
PA
310
311/* Update dfs referral path of superblock */
312static int update_server_fullpath(struct TCP_Server_Info *server, struct cifs_sb_info *cifs_sb,
313 const char *target)
314{
315 int rc = 0;
316 size_t len = strlen(target);
317 char *refpath, *npath;
318
319 if (unlikely(len < 2 || *target != '\\'))
320 return -EINVAL;
321
322 if (target[1] == '\\') {
323 len += 1;
324 refpath = kmalloc(len, GFP_KERNEL);
325 if (!refpath)
326 return -ENOMEM;
327
328 scnprintf(refpath, len, "%s", target);
329 } else {
330 len += sizeof("\\");
331 refpath = kmalloc(len, GFP_KERNEL);
332 if (!refpath)
333 return -ENOMEM;
334
335 scnprintf(refpath, len, "\\%s", target);
336 }
337
338 npath = dfs_cache_canonical_path(refpath, cifs_sb->local_nls, cifs_remap(cifs_sb));
339 kfree(refpath);
340
341 if (IS_ERR(npath)) {
342 rc = PTR_ERR(npath);
343 } else {
344 mutex_lock(&server->refpath_lock);
345 kfree(server->leaf_fullpath);
346 server->leaf_fullpath = npath;
347 mutex_unlock(&server->refpath_lock);
348 server->current_fullpath = server->leaf_fullpath;
349 }
350 return rc;
351}
352
39a154fc
PA
353static int target_share_matches_server(struct TCP_Server_Info *server, char *share,
354 bool *target_match)
1d04a6fe
PA
355{
356 int rc = 0;
357 const char *dfs_host;
358 size_t dfs_host_len;
359
360 *target_match = true;
361 extract_unc_hostname(share, &dfs_host, &dfs_host_len);
362
363 /* Check if hostnames or addresses match */
39a154fc
PA
364 cifs_server_lock(server);
365 if (dfs_host_len != strlen(server->hostname) ||
366 strncasecmp(dfs_host, server->hostname, dfs_host_len)) {
367 cifs_dbg(FYI, "%s: %.*s doesn't match %s\n", __func__,
368 (int)dfs_host_len, dfs_host, server->hostname);
1d04a6fe
PA
369 rc = match_target_ip(server, dfs_host, dfs_host_len, target_match);
370 if (rc)
371 cifs_dbg(VFS, "%s: failed to match target ip: %d\n", __func__, rc);
372 }
39a154fc 373 cifs_server_unlock(server);
1d04a6fe
PA
374 return rc;
375}
376
377static int __tree_connect_dfs_target(const unsigned int xid, struct cifs_tcon *tcon,
378 struct cifs_sb_info *cifs_sb, char *tree, bool islink,
379 struct dfs_cache_tgt_list *tl)
380{
381 int rc;
382 struct TCP_Server_Info *server = tcon->ses->server;
383 const struct smb_version_operations *ops = server->ops;
384 struct cifs_ses *root_ses = CIFS_DFS_ROOT_SES(tcon->ses);
385 struct cifs_tcon *ipc = root_ses->tcon_ipc;
386 char *share = NULL, *prefix = NULL;
1d04a6fe
PA
387 struct dfs_cache_tgt_iterator *tit;
388 bool target_match;
389
1d04a6fe
PA
390 tit = dfs_cache_get_tgt_iterator(tl);
391 if (!tit) {
392 rc = -ENOENT;
393 goto out;
394 }
395
396 /* Try to tree connect to all dfs targets */
397 for (; tit; tit = dfs_cache_get_next_tgt(tl, tit)) {
398 const char *target = dfs_cache_get_tgt_name(tit);
399 struct dfs_cache_tgt_list ntl = DFS_CACHE_TGT_LIST_INIT(ntl);
400
401 kfree(share);
402 kfree(prefix);
403 share = prefix = NULL;
404
405 /* Check if share matches with tcp ses */
406 rc = dfs_cache_get_tgt_share(server->current_fullpath + 1, tit, &share, &prefix);
407 if (rc) {
408 cifs_dbg(VFS, "%s: failed to parse target share: %d\n", __func__, rc);
409 break;
410 }
411
39a154fc 412 rc = target_share_matches_server(server, share, &target_match);
1d04a6fe
PA
413 if (rc)
414 break;
415 if (!target_match) {
416 rc = -EHOSTUNREACH;
417 continue;
418 }
419
420 dfs_cache_noreq_update_tgthint(server->current_fullpath + 1, tit);
421
422 if (ipc->need_reconnect) {
423 scnprintf(tree, MAX_TREE_SIZE, "\\\\%s\\IPC$", server->hostname);
424 rc = ops->tree_connect(xid, ipc->ses, tree, ipc, cifs_sb->local_nls);
9e6002c8 425 cifs_dbg(FYI, "%s: reconnect ipc: %d\n", __func__, rc);
1d04a6fe
PA
426 }
427
428 scnprintf(tree, MAX_TREE_SIZE, "\\%s", share);
429 if (!islink) {
430 rc = ops->tree_connect(xid, tcon->ses, tree, tcon, cifs_sb->local_nls);
431 break;
432 }
433 /*
434 * If no dfs referrals were returned from link target, then just do a TREE_CONNECT
435 * to it. Otherwise, cache the dfs referral and then mark current tcp ses for
436 * reconnect so either the demultiplex thread or the echo worker will reconnect to
437 * newly resolved target.
438 */
439 if (dfs_cache_find(xid, root_ses, cifs_sb->local_nls, cifs_remap(cifs_sb), target,
440 NULL, &ntl)) {
441 rc = ops->tree_connect(xid, tcon->ses, tree, tcon, cifs_sb->local_nls);
442 if (rc)
443 continue;
444
445 rc = cifs_update_super_prepath(cifs_sb, prefix);
446 } else {
447 /* Target is another dfs share */
448 rc = update_server_fullpath(server, cifs_sb, target);
449 dfs_cache_free_tgts(tl);
450
451 if (!rc) {
452 rc = -EREMOTE;
453 list_replace_init(&ntl.tl_list, &tl->tl_list);
454 } else
455 dfs_cache_free_tgts(&ntl);
456 }
457 break;
458 }
459
460out:
461 kfree(share);
462 kfree(prefix);
463
464 return rc;
465}
466
467static int tree_connect_dfs_target(const unsigned int xid, struct cifs_tcon *tcon,
468 struct cifs_sb_info *cifs_sb, char *tree, bool islink,
469 struct dfs_cache_tgt_list *tl)
470{
471 int rc;
472 int num_links = 0;
473 struct TCP_Server_Info *server = tcon->ses->server;
6fbdd5ab 474 char *old_fullpath = server->leaf_fullpath;
1d04a6fe
PA
475
476 do {
477 rc = __tree_connect_dfs_target(xid, tcon, cifs_sb, tree, islink, tl);
478 if (!rc || rc != -EREMOTE)
479 break;
480 } while (rc = -ELOOP, ++num_links < MAX_NESTED_LINKS);
481 /*
6fbdd5ab
PA
482 * If we couldn't tree connect to any targets from last referral path, then
483 * retry it from newly resolved dfs referral.
1d04a6fe 484 */
6fbdd5ab 485 if (rc && server->leaf_fullpath != old_fullpath)
1d04a6fe 486 cifs_signal_cifsd_for_reconnect(server, true);
1d04a6fe
PA
487
488 dfs_cache_free_tgts(tl);
489 return rc;
490}
491
492int cifs_tree_connect(const unsigned int xid, struct cifs_tcon *tcon, const struct nls_table *nlsc)
493{
494 int rc;
495 struct TCP_Server_Info *server = tcon->ses->server;
496 const struct smb_version_operations *ops = server->ops;
497 struct super_block *sb = NULL;
498 struct cifs_sb_info *cifs_sb;
499 struct dfs_cache_tgt_list tl = DFS_CACHE_TGT_LIST_INIT(tl);
500 char *tree;
501 struct dfs_info3_param ref = {0};
502
503 /* only send once per connect */
504 spin_lock(&tcon->tc_lock);
2f0e4f03
SP
505 if (tcon->status != TID_NEW &&
506 tcon->status != TID_NEED_TCON) {
507 spin_unlock(&tcon->tc_lock);
508 return -EHOSTDOWN;
509 }
510
511 if (tcon->status == TID_GOOD) {
1d04a6fe
PA
512 spin_unlock(&tcon->tc_lock);
513 return 0;
514 }
515 tcon->status = TID_IN_TCON;
516 spin_unlock(&tcon->tc_lock);
517
518 tree = kzalloc(MAX_TREE_SIZE, GFP_KERNEL);
519 if (!tree) {
520 rc = -ENOMEM;
521 goto out;
522 }
523
524 if (tcon->ipc) {
39a154fc 525 cifs_server_lock(server);
1d04a6fe 526 scnprintf(tree, MAX_TREE_SIZE, "\\\\%s\\IPC$", server->hostname);
39a154fc 527 cifs_server_unlock(server);
1d04a6fe
PA
528 rc = ops->tree_connect(xid, tcon->ses, tree, tcon, nlsc);
529 goto out;
530 }
531
532 sb = cifs_get_tcp_super(server);
533 if (IS_ERR(sb)) {
534 rc = PTR_ERR(sb);
535 cifs_dbg(VFS, "%s: could not find superblock: %d\n", __func__, rc);
536 goto out;
537 }
538
539 cifs_sb = CIFS_SB(sb);
540
541 /* If it is not dfs or there was no cached dfs referral, then reconnect to same share */
542 if (!server->current_fullpath ||
543 dfs_cache_noreq_find(server->current_fullpath + 1, &ref, &tl)) {
544 rc = ops->tree_connect(xid, tcon->ses, tcon->tree_name, tcon, cifs_sb->local_nls);
545 goto out;
546 }
547
548 rc = tree_connect_dfs_target(xid, tcon, cifs_sb, tree, ref.server_type == DFS_TYPE_LINK,
549 &tl);
550 free_dfs_info_param(&ref);
551
552out:
553 kfree(tree);
554 cifs_put_tcp_super(sb);
555
556 if (rc) {
557 spin_lock(&tcon->tc_lock);
558 if (tcon->status == TID_IN_TCON)
559 tcon->status = TID_NEED_TCON;
560 spin_unlock(&tcon->tc_lock);
561 } else {
562 spin_lock(&tcon->tc_lock);
563 if (tcon->status == TID_IN_TCON)
564 tcon->status = TID_GOOD;
565 spin_unlock(&tcon->tc_lock);
566 tcon->need_reconnect = false;
567 }
568
569 return rc;
570}