xref: /openbmc/linux/fs/smb/client/dfs.c (revision e8069f5a)
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  */
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 static int get_session(struct cifs_mount_ctx *mnt_ctx, const char *full_path)
58 {
59 	struct smb3_fs_context *ctx = mnt_ctx->fs_ctx;
60 	int rc;
61 
62 	ctx->leaf_fullpath = (char *)full_path;
63 	rc = cifs_mount_get_session(mnt_ctx);
64 	ctx->leaf_fullpath = NULL;
65 
66 	return rc;
67 }
68 
69 static int add_root_smb_session(struct cifs_mount_ctx *mnt_ctx)
70 {
71 	struct smb3_fs_context *ctx = mnt_ctx->fs_ctx;
72 	struct dfs_root_ses *root_ses;
73 	struct cifs_ses *ses = mnt_ctx->ses;
74 
75 	if (ses) {
76 		root_ses = kmalloc(sizeof(*root_ses), GFP_KERNEL);
77 		if (!root_ses)
78 			return -ENOMEM;
79 
80 		INIT_LIST_HEAD(&root_ses->list);
81 
82 		spin_lock(&cifs_tcp_ses_lock);
83 		ses->ses_count++;
84 		spin_unlock(&cifs_tcp_ses_lock);
85 		root_ses->ses = ses;
86 		list_add_tail(&root_ses->list, &mnt_ctx->dfs_ses_list);
87 	}
88 	ctx->dfs_root_ses = ses;
89 	return 0;
90 }
91 
92 static int get_dfs_conn(struct cifs_mount_ctx *mnt_ctx, const char *ref_path, const char *full_path,
93 			const struct dfs_cache_tgt_iterator *tit)
94 {
95 	struct smb3_fs_context *ctx = mnt_ctx->fs_ctx;
96 	struct dfs_info3_param ref = {};
97 	bool is_refsrv;
98 	int rc, rc2;
99 
100 	rc = dfs_cache_get_tgt_referral(ref_path + 1, tit, &ref);
101 	if (rc)
102 		return rc;
103 
104 	rc = dfs_parse_target_referral(full_path + 1, &ref, ctx);
105 	if (rc)
106 		goto out;
107 
108 	cifs_mount_put_conns(mnt_ctx);
109 	rc = get_session(mnt_ctx, ref_path);
110 	if (rc)
111 		goto out;
112 
113 	is_refsrv = !!(ref.flags & DFSREF_REFERRAL_SERVER);
114 
115 	rc = -EREMOTE;
116 	if (ref.flags & DFSREF_STORAGE_SERVER) {
117 		rc = cifs_mount_get_tcon(mnt_ctx);
118 		if (rc)
119 			goto out;
120 
121 		/* some servers may not advertise referral capability under ref.flags */
122 		is_refsrv |= is_tcon_dfs(mnt_ctx->tcon);
123 
124 		rc = cifs_is_path_remote(mnt_ctx);
125 	}
126 
127 	dfs_cache_noreq_update_tgthint(ref_path + 1, tit);
128 
129 	if (rc == -EREMOTE && is_refsrv) {
130 		rc2 = add_root_smb_session(mnt_ctx);
131 		if (rc2)
132 			rc = rc2;
133 	}
134 
135 out:
136 	free_dfs_info_param(&ref);
137 	return rc;
138 }
139 
140 static int __dfs_mount_share(struct cifs_mount_ctx *mnt_ctx)
141 {
142 	struct cifs_sb_info *cifs_sb = mnt_ctx->cifs_sb;
143 	struct smb3_fs_context *ctx = mnt_ctx->fs_ctx;
144 	char *ref_path = NULL, *full_path = NULL;
145 	struct dfs_cache_tgt_iterator *tit;
146 	struct TCP_Server_Info *server;
147 	struct cifs_tcon *tcon;
148 	char *origin_fullpath = NULL;
149 	char sep = CIFS_DIR_SEP(cifs_sb);
150 	int num_links = 0;
151 	int rc;
152 
153 	ref_path = dfs_get_path(cifs_sb, ctx->UNC);
154 	if (IS_ERR(ref_path))
155 		return PTR_ERR(ref_path);
156 
157 	full_path = smb3_fs_context_fullpath(ctx, sep);
158 	if (IS_ERR(full_path)) {
159 		rc = PTR_ERR(full_path);
160 		full_path = NULL;
161 		goto out;
162 	}
163 
164 	origin_fullpath = kstrdup(full_path, GFP_KERNEL);
165 	if (!origin_fullpath) {
166 		rc = -ENOMEM;
167 		goto out;
168 	}
169 
170 	do {
171 		struct dfs_cache_tgt_list tl = DFS_CACHE_TGT_LIST_INIT(tl);
172 
173 		rc = dfs_get_referral(mnt_ctx, ref_path + 1, NULL, &tl);
174 		if (rc)
175 			break;
176 
177 		tit = dfs_cache_get_tgt_iterator(&tl);
178 		if (!tit) {
179 			cifs_dbg(VFS, "%s: dfs referral (%s) with no targets\n", __func__,
180 				 ref_path + 1);
181 			rc = -ENOENT;
182 			dfs_cache_free_tgts(&tl);
183 			break;
184 		}
185 
186 		do {
187 			rc = get_dfs_conn(mnt_ctx, ref_path, full_path, tit);
188 			if (!rc)
189 				break;
190 			if (rc == -EREMOTE) {
191 				if (++num_links > MAX_NESTED_LINKS) {
192 					rc = -ELOOP;
193 					break;
194 				}
195 				kfree(ref_path);
196 				kfree(full_path);
197 				ref_path = full_path = NULL;
198 
199 				full_path = smb3_fs_context_fullpath(ctx, sep);
200 				if (IS_ERR(full_path)) {
201 					rc = PTR_ERR(full_path);
202 					full_path = NULL;
203 				} else {
204 					ref_path = dfs_get_path(cifs_sb, full_path);
205 					if (IS_ERR(ref_path)) {
206 						rc = PTR_ERR(ref_path);
207 						ref_path = NULL;
208 					}
209 				}
210 				break;
211 			}
212 		} while ((tit = dfs_cache_get_next_tgt(&tl, tit)));
213 		dfs_cache_free_tgts(&tl);
214 	} while (rc == -EREMOTE);
215 
216 	if (!rc) {
217 		server = mnt_ctx->server;
218 		tcon = mnt_ctx->tcon;
219 
220 		spin_lock(&tcon->tc_lock);
221 		if (!tcon->origin_fullpath) {
222 			tcon->origin_fullpath = origin_fullpath;
223 			origin_fullpath = NULL;
224 		}
225 		spin_unlock(&tcon->tc_lock);
226 
227 		if (list_empty(&tcon->dfs_ses_list)) {
228 			list_replace_init(&mnt_ctx->dfs_ses_list,
229 					  &tcon->dfs_ses_list);
230 			queue_delayed_work(dfscache_wq, &tcon->dfs_cache_work,
231 					   dfs_cache_get_ttl() * HZ);
232 		} else {
233 			dfs_put_root_smb_sessions(&mnt_ctx->dfs_ses_list);
234 		}
235 	}
236 
237 out:
238 	kfree(origin_fullpath);
239 	kfree(ref_path);
240 	kfree(full_path);
241 	return rc;
242 }
243 
244 int dfs_mount_share(struct cifs_mount_ctx *mnt_ctx, bool *isdfs)
245 {
246 	struct smb3_fs_context *ctx = mnt_ctx->fs_ctx;
247 	struct cifs_ses *ses;
248 	bool nodfs = ctx->nodfs;
249 	int rc;
250 
251 	*isdfs = false;
252 	rc = get_session(mnt_ctx, NULL);
253 	if (rc)
254 		return rc;
255 
256 	ctx->dfs_root_ses = mnt_ctx->ses;
257 	/*
258 	 * If called with 'nodfs' mount option, then skip DFS resolving.  Otherwise unconditionally
259 	 * try to get an DFS referral (even cached) to determine whether it is an DFS mount.
260 	 *
261 	 * Skip prefix path to provide support for DFS referrals from w2k8 servers which don't seem
262 	 * to respond with PATH_NOT_COVERED to requests that include the prefix.
263 	 */
264 	if (!nodfs) {
265 		rc = dfs_get_referral(mnt_ctx, ctx->UNC + 1, NULL, NULL);
266 		if (rc) {
267 			cifs_dbg(FYI, "%s: no dfs referral for %s: %d\n",
268 				 __func__, ctx->UNC + 1, rc);
269 			cifs_dbg(FYI, "%s: assuming non-dfs mount...\n", __func__);
270 			nodfs = true;
271 		}
272 	}
273 	if (nodfs) {
274 		rc = cifs_mount_get_tcon(mnt_ctx);
275 		if (!rc)
276 			rc = cifs_is_path_remote(mnt_ctx);
277 		return rc;
278 	}
279 
280 	*isdfs = true;
281 	/*
282 	 * Prevent DFS root session of being put in the first call to
283 	 * cifs_mount_put_conns().  If another DFS root server was not found
284 	 * while chasing the referrals (@ctx->dfs_root_ses == @ses), then we
285 	 * can safely put extra refcount of @ses.
286 	 */
287 	ses = mnt_ctx->ses;
288 	mnt_ctx->ses = NULL;
289 	mnt_ctx->server = NULL;
290 	rc = __dfs_mount_share(mnt_ctx);
291 	if (ses == ctx->dfs_root_ses)
292 		cifs_put_smb_ses(ses);
293 
294 	return rc;
295 }
296 
297 /* Update dfs referral path of superblock */
298 static int update_server_fullpath(struct TCP_Server_Info *server, struct cifs_sb_info *cifs_sb,
299 				  const char *target)
300 {
301 	int rc = 0;
302 	size_t len = strlen(target);
303 	char *refpath, *npath;
304 
305 	if (unlikely(len < 2 || *target != '\\'))
306 		return -EINVAL;
307 
308 	if (target[1] == '\\') {
309 		len += 1;
310 		refpath = kmalloc(len, GFP_KERNEL);
311 		if (!refpath)
312 			return -ENOMEM;
313 
314 		scnprintf(refpath, len, "%s", target);
315 	} else {
316 		len += sizeof("\\");
317 		refpath = kmalloc(len, GFP_KERNEL);
318 		if (!refpath)
319 			return -ENOMEM;
320 
321 		scnprintf(refpath, len, "\\%s", target);
322 	}
323 
324 	npath = dfs_cache_canonical_path(refpath, cifs_sb->local_nls, cifs_remap(cifs_sb));
325 	kfree(refpath);
326 
327 	if (IS_ERR(npath)) {
328 		rc = PTR_ERR(npath);
329 	} else {
330 		mutex_lock(&server->refpath_lock);
331 		spin_lock(&server->srv_lock);
332 		kfree(server->leaf_fullpath);
333 		server->leaf_fullpath = npath;
334 		spin_unlock(&server->srv_lock);
335 		mutex_unlock(&server->refpath_lock);
336 	}
337 	return rc;
338 }
339 
340 static int target_share_matches_server(struct TCP_Server_Info *server, char *share,
341 				       bool *target_match)
342 {
343 	int rc = 0;
344 	const char *dfs_host;
345 	size_t dfs_host_len;
346 
347 	*target_match = true;
348 	extract_unc_hostname(share, &dfs_host, &dfs_host_len);
349 
350 	/* Check if hostnames or addresses match */
351 	cifs_server_lock(server);
352 	if (dfs_host_len != strlen(server->hostname) ||
353 	    strncasecmp(dfs_host, server->hostname, dfs_host_len)) {
354 		cifs_dbg(FYI, "%s: %.*s doesn't match %s\n", __func__,
355 			 (int)dfs_host_len, dfs_host, server->hostname);
356 		rc = match_target_ip(server, dfs_host, dfs_host_len, target_match);
357 		if (rc)
358 			cifs_dbg(VFS, "%s: failed to match target ip: %d\n", __func__, rc);
359 	}
360 	cifs_server_unlock(server);
361 	return rc;
362 }
363 
364 static void __tree_connect_ipc(const unsigned int xid, char *tree,
365 			       struct cifs_sb_info *cifs_sb,
366 			       struct cifs_ses *ses)
367 {
368 	struct TCP_Server_Info *server = ses->server;
369 	struct cifs_tcon *tcon = ses->tcon_ipc;
370 	int rc;
371 
372 	spin_lock(&ses->ses_lock);
373 	spin_lock(&ses->chan_lock);
374 	if (cifs_chan_needs_reconnect(ses, server) ||
375 	    ses->ses_status != SES_GOOD) {
376 		spin_unlock(&ses->chan_lock);
377 		spin_unlock(&ses->ses_lock);
378 		cifs_server_dbg(FYI, "%s: skipping ipc reconnect due to disconnected ses\n",
379 				__func__);
380 		return;
381 	}
382 	spin_unlock(&ses->chan_lock);
383 	spin_unlock(&ses->ses_lock);
384 
385 	cifs_server_lock(server);
386 	scnprintf(tree, MAX_TREE_SIZE, "\\\\%s\\IPC$", server->hostname);
387 	cifs_server_unlock(server);
388 
389 	rc = server->ops->tree_connect(xid, ses, tree, tcon,
390 				       cifs_sb->local_nls);
391 	cifs_server_dbg(FYI, "%s: tree_reconnect %s: %d\n", __func__, tree, rc);
392 	spin_lock(&tcon->tc_lock);
393 	if (rc) {
394 		tcon->status = TID_NEED_TCON;
395 	} else {
396 		tcon->status = TID_GOOD;
397 		tcon->need_reconnect = false;
398 	}
399 	spin_unlock(&tcon->tc_lock);
400 }
401 
402 static void tree_connect_ipc(const unsigned int xid, char *tree,
403 			     struct cifs_sb_info *cifs_sb,
404 			     struct cifs_tcon *tcon)
405 {
406 	struct cifs_ses *ses = tcon->ses;
407 
408 	__tree_connect_ipc(xid, tree, cifs_sb, ses);
409 	__tree_connect_ipc(xid, tree, cifs_sb, CIFS_DFS_ROOT_SES(ses));
410 }
411 
412 static int __tree_connect_dfs_target(const unsigned int xid, struct cifs_tcon *tcon,
413 				     struct cifs_sb_info *cifs_sb, char *tree, bool islink,
414 				     struct dfs_cache_tgt_list *tl)
415 {
416 	int rc;
417 	struct TCP_Server_Info *server = tcon->ses->server;
418 	const struct smb_version_operations *ops = server->ops;
419 	struct cifs_ses *root_ses = CIFS_DFS_ROOT_SES(tcon->ses);
420 	char *share = NULL, *prefix = NULL;
421 	struct dfs_cache_tgt_iterator *tit;
422 	bool target_match;
423 
424 	tit = dfs_cache_get_tgt_iterator(tl);
425 	if (!tit) {
426 		rc = -ENOENT;
427 		goto out;
428 	}
429 
430 	/* Try to tree connect to all dfs targets */
431 	for (; tit; tit = dfs_cache_get_next_tgt(tl, tit)) {
432 		const char *target = dfs_cache_get_tgt_name(tit);
433 		struct dfs_cache_tgt_list ntl = DFS_CACHE_TGT_LIST_INIT(ntl);
434 
435 		kfree(share);
436 		kfree(prefix);
437 		share = prefix = NULL;
438 
439 		/* Check if share matches with tcp ses */
440 		rc = dfs_cache_get_tgt_share(server->leaf_fullpath + 1, tit, &share, &prefix);
441 		if (rc) {
442 			cifs_dbg(VFS, "%s: failed to parse target share: %d\n", __func__, rc);
443 			break;
444 		}
445 
446 		rc = target_share_matches_server(server, share, &target_match);
447 		if (rc)
448 			break;
449 		if (!target_match) {
450 			rc = -EHOSTUNREACH;
451 			continue;
452 		}
453 
454 		dfs_cache_noreq_update_tgthint(server->leaf_fullpath + 1, tit);
455 		tree_connect_ipc(xid, tree, cifs_sb, tcon);
456 
457 		scnprintf(tree, MAX_TREE_SIZE, "\\%s", share);
458 		if (!islink) {
459 			rc = ops->tree_connect(xid, tcon->ses, tree, tcon, cifs_sb->local_nls);
460 			break;
461 		}
462 
463 		/*
464 		 * If no dfs referrals were returned from link target, then just do a TREE_CONNECT
465 		 * to it.  Otherwise, cache the dfs referral and then mark current tcp ses for
466 		 * reconnect so either the demultiplex thread or the echo worker will reconnect to
467 		 * newly resolved target.
468 		 */
469 		if (dfs_cache_find(xid, root_ses, cifs_sb->local_nls, cifs_remap(cifs_sb), target,
470 				   NULL, &ntl)) {
471 			rc = ops->tree_connect(xid, tcon->ses, tree, tcon, cifs_sb->local_nls);
472 			if (rc)
473 				continue;
474 
475 			rc = cifs_update_super_prepath(cifs_sb, prefix);
476 		} else {
477 			/* Target is another dfs share */
478 			rc = update_server_fullpath(server, cifs_sb, target);
479 			dfs_cache_free_tgts(tl);
480 
481 			if (!rc) {
482 				rc = -EREMOTE;
483 				list_replace_init(&ntl.tl_list, &tl->tl_list);
484 			} else
485 				dfs_cache_free_tgts(&ntl);
486 		}
487 		break;
488 	}
489 
490 out:
491 	kfree(share);
492 	kfree(prefix);
493 
494 	return rc;
495 }
496 
497 static int tree_connect_dfs_target(const unsigned int xid, struct cifs_tcon *tcon,
498 				   struct cifs_sb_info *cifs_sb, char *tree, bool islink,
499 				   struct dfs_cache_tgt_list *tl)
500 {
501 	int rc;
502 	int num_links = 0;
503 	struct TCP_Server_Info *server = tcon->ses->server;
504 	char *old_fullpath = server->leaf_fullpath;
505 
506 	do {
507 		rc = __tree_connect_dfs_target(xid, tcon, cifs_sb, tree, islink, tl);
508 		if (!rc || rc != -EREMOTE)
509 			break;
510 	} while (rc = -ELOOP, ++num_links < MAX_NESTED_LINKS);
511 	/*
512 	 * If we couldn't tree connect to any targets from last referral path, then
513 	 * retry it from newly resolved dfs referral.
514 	 */
515 	if (rc && server->leaf_fullpath != old_fullpath)
516 		cifs_signal_cifsd_for_reconnect(server, true);
517 
518 	dfs_cache_free_tgts(tl);
519 	return rc;
520 }
521 
522 int cifs_tree_connect(const unsigned int xid, struct cifs_tcon *tcon, const struct nls_table *nlsc)
523 {
524 	int rc;
525 	struct TCP_Server_Info *server = tcon->ses->server;
526 	const struct smb_version_operations *ops = server->ops;
527 	struct dfs_cache_tgt_list tl = DFS_CACHE_TGT_LIST_INIT(tl);
528 	struct cifs_sb_info *cifs_sb = NULL;
529 	struct super_block *sb = NULL;
530 	struct dfs_info3_param ref = {0};
531 	char *tree;
532 
533 	/* only send once per connect */
534 	spin_lock(&tcon->tc_lock);
535 	if (tcon->status == TID_GOOD) {
536 		spin_unlock(&tcon->tc_lock);
537 		return 0;
538 	}
539 
540 	if (tcon->status != TID_NEW &&
541 	    tcon->status != TID_NEED_TCON) {
542 		spin_unlock(&tcon->tc_lock);
543 		return -EHOSTDOWN;
544 	}
545 
546 	tcon->status = TID_IN_TCON;
547 	spin_unlock(&tcon->tc_lock);
548 
549 	tree = kzalloc(MAX_TREE_SIZE, GFP_KERNEL);
550 	if (!tree) {
551 		rc = -ENOMEM;
552 		goto out;
553 	}
554 
555 	if (tcon->ipc) {
556 		cifs_server_lock(server);
557 		scnprintf(tree, MAX_TREE_SIZE, "\\\\%s\\IPC$", server->hostname);
558 		cifs_server_unlock(server);
559 		rc = ops->tree_connect(xid, tcon->ses, tree, tcon, nlsc);
560 		goto out;
561 	}
562 
563 	sb = cifs_get_dfs_tcon_super(tcon);
564 	if (!IS_ERR(sb))
565 		cifs_sb = CIFS_SB(sb);
566 
567 	/*
568 	 * Tree connect to last share in @tcon->tree_name whether dfs super or
569 	 * cached dfs referral was not found.
570 	 */
571 	if (!cifs_sb || !server->leaf_fullpath ||
572 	    dfs_cache_noreq_find(server->leaf_fullpath + 1, &ref, &tl)) {
573 		rc = ops->tree_connect(xid, tcon->ses, tcon->tree_name, tcon,
574 				       cifs_sb ? cifs_sb->local_nls : nlsc);
575 		goto out;
576 	}
577 
578 	rc = tree_connect_dfs_target(xid, tcon, cifs_sb, tree, ref.server_type == DFS_TYPE_LINK,
579 				     &tl);
580 	free_dfs_info_param(&ref);
581 
582 out:
583 	kfree(tree);
584 	cifs_put_tcp_super(sb);
585 
586 	if (rc) {
587 		spin_lock(&tcon->tc_lock);
588 		if (tcon->status == TID_IN_TCON)
589 			tcon->status = TID_NEED_TCON;
590 		spin_unlock(&tcon->tc_lock);
591 	} else {
592 		spin_lock(&tcon->tc_lock);
593 		if (tcon->status == TID_IN_TCON)
594 			tcon->status = TID_GOOD;
595 		spin_unlock(&tcon->tc_lock);
596 		tcon->need_reconnect = false;
597 	}
598 
599 	return rc;
600 }
601