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