brintos

brintos / linux-shallow public Read only

0
0
Text · 15.5 KiB · 3f6077c Raw
633 lines · c
1// SPDX-License-Identifier: GPL-2.02/*3 * Copyright (c) 2022 Paulo Alcantara <palcantara@suse.de>4 */5 6#include "cifsproto.h"7#include "cifs_debug.h"8#include "dns_resolve.h"9#include "fs_context.h"10#include "dfs.h"11 12/**13 * dfs_parse_target_referral - set fs context for dfs target referral14 *15 * @full_path: full path in UNC format.16 * @ref: dfs referral pointer.17 * @ctx: smb3 fs context pointer.18 *19 * Return zero if dfs referral was parsed correctly, otherwise non-zero.20 */21int dfs_parse_target_referral(const char *full_path, const struct dfs_info3_param *ref,22			      struct smb3_fs_context *ctx)23{24	int rc;25	const char *prepath = NULL;26	char *path;27 28	if (!full_path || !*full_path || !ref || !ctx)29		return -EINVAL;30 31	if (WARN_ON_ONCE(!ref->node_name || ref->path_consumed < 0))32		return -EINVAL;33 34	if (strlen(full_path) - ref->path_consumed) {35		prepath = full_path + ref->path_consumed;36		/* skip initial delimiter */37		if (*prepath == '/' || *prepath == '\\')38			prepath++;39	}40 41	path = cifs_build_devname(ref->node_name, prepath);42	if (IS_ERR(path))43		return PTR_ERR(path);44 45	rc = smb3_parse_devname(path, ctx);46	if (rc)47		goto out;48 49	rc = dns_resolve_server_name_to_ip(path, (struct sockaddr *)&ctx->dstaddr, NULL);50 51out:52	kfree(path);53	return rc;54}55 56static int get_session(struct cifs_mount_ctx *mnt_ctx, const char *full_path)57{58	struct smb3_fs_context *ctx = mnt_ctx->fs_ctx;59	int rc;60 61	ctx->leaf_fullpath = (char *)full_path;62	rc = cifs_mount_get_session(mnt_ctx);63	ctx->leaf_fullpath = NULL;64 65	return rc;66}67 68/*69 * Get an active reference of @ses so that next call to cifs_put_tcon() won't70 * release it as any new DFS referrals must go through its IPC tcon.71 */72static void set_root_smb_session(struct cifs_mount_ctx *mnt_ctx)73{74	struct smb3_fs_context *ctx = mnt_ctx->fs_ctx;75	struct cifs_ses *ses = mnt_ctx->ses;76 77	if (ses) {78		spin_lock(&cifs_tcp_ses_lock);79		cifs_smb_ses_inc_refcount(ses);80		spin_unlock(&cifs_tcp_ses_lock);81	}82	ctx->dfs_root_ses = ses;83}84 85static inline int parse_dfs_target(struct smb3_fs_context *ctx,86				   struct dfs_ref_walk *rw,87				   struct dfs_info3_param *tgt)88{89	int rc;90	const char *fpath = ref_walk_fpath(rw) + 1;91 92	rc = ref_walk_get_tgt(rw, tgt);93	if (!rc)94		rc = dfs_parse_target_referral(fpath, tgt, ctx);95	return rc;96}97 98static int setup_dfs_ref(struct cifs_mount_ctx *mnt_ctx,99			 struct dfs_info3_param *tgt,100			 struct dfs_ref_walk *rw)101{102	struct smb3_fs_context *ctx = mnt_ctx->fs_ctx;103	struct cifs_sb_info *cifs_sb = mnt_ctx->cifs_sb;104	char *ref_path, *full_path;105	int rc;106 107	full_path = smb3_fs_context_fullpath(ctx, CIFS_DIR_SEP(cifs_sb));108	if (IS_ERR(full_path))109		return PTR_ERR(full_path);110 111	if (!tgt || (tgt->server_type == DFS_TYPE_LINK &&112		     DFS_INTERLINK(tgt->flags)))113		ref_path = dfs_get_path(cifs_sb, ctx->UNC);114	else115		ref_path = dfs_get_path(cifs_sb, full_path);116	if (IS_ERR(ref_path)) {117		rc = PTR_ERR(ref_path);118		kfree(full_path);119		return rc;120	}121	ref_walk_path(rw) = ref_path;122	ref_walk_fpath(rw) = full_path;123	ref_walk_ses(rw) = ctx->dfs_root_ses;124	return 0;125}126 127static int __dfs_referral_walk(struct cifs_mount_ctx *mnt_ctx,128			       struct dfs_ref_walk *rw)129{130	struct smb3_fs_context *ctx = mnt_ctx->fs_ctx;131	struct dfs_info3_param tgt = {};132	int rc = -ENOENT;133 134again:135	do {136		ctx->dfs_root_ses = ref_walk_ses(rw);137		if (ref_walk_empty(rw)) {138			rc = dfs_get_referral(mnt_ctx, ref_walk_path(rw) + 1,139					      NULL, ref_walk_tl(rw));140			if (rc) {141				rc = cifs_mount_get_tcon(mnt_ctx);142				if (!rc)143					rc = cifs_is_path_remote(mnt_ctx);144				continue;145			}146			if (!ref_walk_num_tgts(rw)) {147				rc = -ENOENT;148				continue;149			}150		}151 152		while (ref_walk_next_tgt(rw)) {153			rc = parse_dfs_target(ctx, rw, &tgt);154			if (rc)155				continue;156 157			cifs_mount_put_conns(mnt_ctx);158			rc = get_session(mnt_ctx, ref_walk_path(rw));159			if (rc)160				continue;161 162			ref_walk_set_tgt_hint(rw);163			if (tgt.flags & DFSREF_STORAGE_SERVER) {164				rc = cifs_mount_get_tcon(mnt_ctx);165				if (!rc)166					rc = cifs_is_path_remote(mnt_ctx);167				if (!rc)168					break;169				if (rc != -EREMOTE)170					continue;171			}172 173			set_root_smb_session(mnt_ctx);174			rc = ref_walk_advance(rw);175			if (!rc) {176				rc = setup_dfs_ref(mnt_ctx, &tgt, rw);177				if (!rc) {178					rc = -EREMOTE;179					goto again;180				}181			}182			if (rc != -ELOOP)183				goto out;184		}185	} while (rc && ref_walk_descend(rw));186 187out:188	free_dfs_info_param(&tgt);189	return rc;190}191 192static int dfs_referral_walk(struct cifs_mount_ctx *mnt_ctx,193			     struct dfs_ref_walk **rw)194{195	int rc;196 197	*rw = ref_walk_alloc();198	if (IS_ERR(*rw)) {199		rc = PTR_ERR(*rw);200		*rw = NULL;201		return rc;202	}203 204	ref_walk_init(*rw);205	rc = setup_dfs_ref(mnt_ctx, NULL, *rw);206	if (!rc)207		rc = __dfs_referral_walk(mnt_ctx, *rw);208	return rc;209}210 211static int __dfs_mount_share(struct cifs_mount_ctx *mnt_ctx)212{213	struct cifs_sb_info *cifs_sb = mnt_ctx->cifs_sb;214	struct smb3_fs_context *ctx = mnt_ctx->fs_ctx;215	struct dfs_ref_walk *rw = NULL;216	struct cifs_tcon *tcon;217	char *origin_fullpath;218	int rc;219 220	origin_fullpath = dfs_get_path(cifs_sb, ctx->source);221	if (IS_ERR(origin_fullpath))222		return PTR_ERR(origin_fullpath);223 224	rc = dfs_referral_walk(mnt_ctx, &rw);225	if (!rc) {226		/*227		 * Prevent superblock from being created with any missing228		 * connections.229		 */230		if (WARN_ON(!mnt_ctx->server))231			rc = -EHOSTDOWN;232		else if (WARN_ON(!mnt_ctx->ses))233			rc = -EACCES;234		else if (WARN_ON(!mnt_ctx->tcon))235			rc = -ENOENT;236	}237	if (rc)238		goto out;239 240	tcon = mnt_ctx->tcon;241	spin_lock(&tcon->tc_lock);242	tcon->origin_fullpath = origin_fullpath;243	origin_fullpath = NULL;244	ref_walk_set_tcon(rw, tcon);245	spin_unlock(&tcon->tc_lock);246	queue_delayed_work(dfscache_wq, &tcon->dfs_cache_work,247			   dfs_cache_get_ttl() * HZ);248 249out:250	kfree(origin_fullpath);251	ref_walk_free(rw);252	return rc;253}254 255/*256 * If @ctx->dfs_automount, then update @ctx->dstaddr earlier with the DFS root257 * server from where we'll start following any referrals.  Otherwise rely on the258 * value provided by mount(2) as the user might not have dns_resolver key set up259 * and therefore failing to upcall to resolve UNC hostname under @ctx->source.260 */261static int update_fs_context_dstaddr(struct smb3_fs_context *ctx)262{263	struct sockaddr *addr = (struct sockaddr *)&ctx->dstaddr;264	int rc = 0;265 266	if (!ctx->nodfs && ctx->dfs_automount) {267		rc = dns_resolve_server_name_to_ip(ctx->source, addr, NULL);268		if (!rc)269			cifs_set_port(addr, ctx->port);270		ctx->dfs_automount = false;271	}272	return rc;273}274 275int dfs_mount_share(struct cifs_mount_ctx *mnt_ctx)276{277	struct smb3_fs_context *ctx = mnt_ctx->fs_ctx;278	bool nodfs = ctx->nodfs;279	int rc;280 281	rc = update_fs_context_dstaddr(ctx);282	if (rc)283		return rc;284 285	rc = get_session(mnt_ctx, NULL);286	if (rc)287		return rc;288 289	/*290	 * If called with 'nodfs' mount option, then skip DFS resolving.  Otherwise unconditionally291	 * try to get an DFS referral (even cached) to determine whether it is an DFS mount.292	 *293	 * Skip prefix path to provide support for DFS referrals from w2k8 servers which don't seem294	 * to respond with PATH_NOT_COVERED to requests that include the prefix.295	 */296	if (!nodfs) {297		rc = dfs_get_referral(mnt_ctx, ctx->UNC + 1, NULL, NULL);298		if (rc) {299			cifs_dbg(FYI, "%s: no dfs referral for %s: %d\n",300				 __func__, ctx->UNC + 1, rc);301			cifs_dbg(FYI, "%s: assuming non-dfs mount...\n", __func__);302			nodfs = true;303		}304	}305	if (nodfs) {306		rc = cifs_mount_get_tcon(mnt_ctx);307		if (!rc)308			rc = cifs_is_path_remote(mnt_ctx);309		return rc;310	}311 312	if (!ctx->dfs_conn) {313		ctx->dfs_conn = true;314		cifs_mount_put_conns(mnt_ctx);315		rc = get_session(mnt_ctx, NULL);316	}317	if (!rc) {318		set_root_smb_session(mnt_ctx);319		rc = __dfs_mount_share(mnt_ctx);320	}321	return rc;322}323 324/* Update dfs referral path of superblock */325static int update_server_fullpath(struct TCP_Server_Info *server, struct cifs_sb_info *cifs_sb,326				  const char *target)327{328	int rc = 0;329	size_t len = strlen(target);330	char *refpath, *npath;331 332	if (unlikely(len < 2 || *target != '\\'))333		return -EINVAL;334 335	if (target[1] == '\\') {336		len += 1;337		refpath = kmalloc(len, GFP_KERNEL);338		if (!refpath)339			return -ENOMEM;340 341		scnprintf(refpath, len, "%s", target);342	} else {343		len += sizeof("\\");344		refpath = kmalloc(len, GFP_KERNEL);345		if (!refpath)346			return -ENOMEM;347 348		scnprintf(refpath, len, "\\%s", target);349	}350 351	npath = dfs_cache_canonical_path(refpath, cifs_sb->local_nls, cifs_remap(cifs_sb));352	kfree(refpath);353 354	if (IS_ERR(npath)) {355		rc = PTR_ERR(npath);356	} else {357		mutex_lock(&server->refpath_lock);358		spin_lock(&server->srv_lock);359		kfree(server->leaf_fullpath);360		server->leaf_fullpath = npath;361		spin_unlock(&server->srv_lock);362		mutex_unlock(&server->refpath_lock);363	}364	return rc;365}366 367static int target_share_matches_server(struct TCP_Server_Info *server, char *share,368				       bool *target_match)369{370	int rc = 0;371	const char *dfs_host;372	size_t dfs_host_len;373 374	*target_match = true;375	extract_unc_hostname(share, &dfs_host, &dfs_host_len);376 377	/* Check if hostnames or addresses match */378	cifs_server_lock(server);379	if (dfs_host_len != strlen(server->hostname) ||380	    strncasecmp(dfs_host, server->hostname, dfs_host_len)) {381		cifs_dbg(FYI, "%s: %.*s doesn't match %s\n", __func__,382			 (int)dfs_host_len, dfs_host, server->hostname);383		rc = match_target_ip(server, dfs_host, dfs_host_len, target_match);384		if (rc)385			cifs_dbg(VFS, "%s: failed to match target ip: %d\n", __func__, rc);386	}387	cifs_server_unlock(server);388	return rc;389}390 391static void __tree_connect_ipc(const unsigned int xid, char *tree,392			       struct cifs_sb_info *cifs_sb,393			       struct cifs_ses *ses)394{395	struct TCP_Server_Info *server = ses->server;396	struct cifs_tcon *tcon = ses->tcon_ipc;397	int rc;398 399	spin_lock(&ses->ses_lock);400	spin_lock(&ses->chan_lock);401	if (cifs_chan_needs_reconnect(ses, server) ||402	    ses->ses_status != SES_GOOD) {403		spin_unlock(&ses->chan_lock);404		spin_unlock(&ses->ses_lock);405		cifs_server_dbg(FYI, "%s: skipping ipc reconnect due to disconnected ses\n",406				__func__);407		return;408	}409	spin_unlock(&ses->chan_lock);410	spin_unlock(&ses->ses_lock);411 412	cifs_server_lock(server);413	scnprintf(tree, MAX_TREE_SIZE, "\\\\%s\\IPC$", server->hostname);414	cifs_server_unlock(server);415 416	rc = server->ops->tree_connect(xid, ses, tree, tcon,417				       cifs_sb->local_nls);418	cifs_server_dbg(FYI, "%s: tree_reconnect %s: %d\n", __func__, tree, rc);419	spin_lock(&tcon->tc_lock);420	if (rc) {421		tcon->status = TID_NEED_TCON;422	} else {423		tcon->status = TID_GOOD;424		tcon->need_reconnect = false;425	}426	spin_unlock(&tcon->tc_lock);427}428 429static void tree_connect_ipc(const unsigned int xid, char *tree,430			     struct cifs_sb_info *cifs_sb,431			     struct cifs_tcon *tcon)432{433	struct cifs_ses *ses = tcon->ses;434 435	__tree_connect_ipc(xid, tree, cifs_sb, ses);436	__tree_connect_ipc(xid, tree, cifs_sb, CIFS_DFS_ROOT_SES(ses));437}438 439static int __tree_connect_dfs_target(const unsigned int xid, struct cifs_tcon *tcon,440				     struct cifs_sb_info *cifs_sb, char *tree, bool islink,441				     struct dfs_cache_tgt_list *tl)442{443	int rc;444	struct TCP_Server_Info *server = tcon->ses->server;445	const struct smb_version_operations *ops = server->ops;446	struct cifs_ses *root_ses = CIFS_DFS_ROOT_SES(tcon->ses);447	char *share = NULL, *prefix = NULL;448	struct dfs_cache_tgt_iterator *tit;449	bool target_match;450 451	tit = dfs_cache_get_tgt_iterator(tl);452	if (!tit) {453		rc = -ENOENT;454		goto out;455	}456 457	/* Try to tree connect to all dfs targets */458	for (; tit; tit = dfs_cache_get_next_tgt(tl, tit)) {459		const char *target = dfs_cache_get_tgt_name(tit);460		DFS_CACHE_TGT_LIST(ntl);461 462		kfree(share);463		kfree(prefix);464		share = prefix = NULL;465 466		/* Check if share matches with tcp ses */467		rc = dfs_cache_get_tgt_share(server->leaf_fullpath + 1, tit, &share, &prefix);468		if (rc) {469			cifs_dbg(VFS, "%s: failed to parse target share: %d\n", __func__, rc);470			break;471		}472 473		rc = target_share_matches_server(server, share, &target_match);474		if (rc)475			break;476		if (!target_match) {477			rc = -EHOSTUNREACH;478			continue;479		}480 481		dfs_cache_noreq_update_tgthint(server->leaf_fullpath + 1, tit);482		tree_connect_ipc(xid, tree, cifs_sb, tcon);483 484		scnprintf(tree, MAX_TREE_SIZE, "\\%s", share);485		if (!islink) {486			rc = ops->tree_connect(xid, tcon->ses, tree, tcon, cifs_sb->local_nls);487			break;488		}489 490		/*491		 * If no dfs referrals were returned from link target, then just do a TREE_CONNECT492		 * to it.  Otherwise, cache the dfs referral and then mark current tcp ses for493		 * reconnect so either the demultiplex thread or the echo worker will reconnect to494		 * newly resolved target.495		 */496		if (dfs_cache_find(xid, root_ses, cifs_sb->local_nls, cifs_remap(cifs_sb), target,497				   NULL, &ntl)) {498			rc = ops->tree_connect(xid, tcon->ses, tree, tcon, cifs_sb->local_nls);499			if (rc)500				continue;501 502			rc = cifs_update_super_prepath(cifs_sb, prefix);503		} else {504			/* Target is another dfs share */505			rc = update_server_fullpath(server, cifs_sb, target);506			dfs_cache_free_tgts(tl);507 508			if (!rc) {509				rc = -EREMOTE;510				list_replace_init(&ntl.tl_list, &tl->tl_list);511			} else512				dfs_cache_free_tgts(&ntl);513		}514		break;515	}516 517out:518	kfree(share);519	kfree(prefix);520 521	return rc;522}523 524static int tree_connect_dfs_target(const unsigned int xid, struct cifs_tcon *tcon,525				   struct cifs_sb_info *cifs_sb, char *tree, bool islink,526				   struct dfs_cache_tgt_list *tl)527{528	int rc;529	int num_links = 0;530	struct TCP_Server_Info *server = tcon->ses->server;531	char *old_fullpath = server->leaf_fullpath;532 533	do {534		rc = __tree_connect_dfs_target(xid, tcon, cifs_sb, tree, islink, tl);535		if (!rc || rc != -EREMOTE)536			break;537	} while (rc = -ELOOP, ++num_links < MAX_NESTED_LINKS);538	/*539	 * If we couldn't tree connect to any targets from last referral path, then540	 * retry it from newly resolved dfs referral.541	 */542	if (rc && server->leaf_fullpath != old_fullpath)543		cifs_signal_cifsd_for_reconnect(server, true);544 545	dfs_cache_free_tgts(tl);546	return rc;547}548 549int cifs_tree_connect(const unsigned int xid, struct cifs_tcon *tcon, const struct nls_table *nlsc)550{551	int rc;552	struct TCP_Server_Info *server = tcon->ses->server;553	const struct smb_version_operations *ops = server->ops;554	DFS_CACHE_TGT_LIST(tl);555	struct cifs_sb_info *cifs_sb = NULL;556	struct super_block *sb = NULL;557	struct dfs_info3_param ref = {0};558	char *tree;559 560	/* only send once per connect */561	spin_lock(&tcon->tc_lock);562 563	/* if tcon is marked for needing reconnect, update state */564	if (tcon->need_reconnect)565		tcon->status = TID_NEED_TCON;566 567	if (tcon->status == TID_GOOD) {568		spin_unlock(&tcon->tc_lock);569		return 0;570	}571 572	if (tcon->status != TID_NEW &&573	    tcon->status != TID_NEED_TCON) {574		spin_unlock(&tcon->tc_lock);575		return -EHOSTDOWN;576	}577 578	tcon->status = TID_IN_TCON;579	spin_unlock(&tcon->tc_lock);580 581	tree = kzalloc(MAX_TREE_SIZE, GFP_KERNEL);582	if (!tree) {583		rc = -ENOMEM;584		goto out;585	}586 587	if (tcon->ipc) {588		cifs_server_lock(server);589		scnprintf(tree, MAX_TREE_SIZE, "\\\\%s\\IPC$", server->hostname);590		cifs_server_unlock(server);591		rc = ops->tree_connect(xid, tcon->ses, tree, tcon, nlsc);592		goto out;593	}594 595	sb = cifs_get_dfs_tcon_super(tcon);596	if (!IS_ERR(sb))597		cifs_sb = CIFS_SB(sb);598 599	/*600	 * Tree connect to last share in @tcon->tree_name whether dfs super or601	 * cached dfs referral was not found.602	 */603	if (!cifs_sb || !server->leaf_fullpath ||604	    dfs_cache_noreq_find(server->leaf_fullpath + 1, &ref, &tl)) {605		rc = ops->tree_connect(xid, tcon->ses, tcon->tree_name, tcon,606				       cifs_sb ? cifs_sb->local_nls : nlsc);607		goto out;608	}609 610	rc = tree_connect_dfs_target(xid, tcon, cifs_sb, tree, ref.server_type == DFS_TYPE_LINK,611				     &tl);612	free_dfs_info_param(&ref);613 614out:615	kfree(tree);616	cifs_put_tcp_super(sb);617 618	if (rc) {619		spin_lock(&tcon->tc_lock);620		if (tcon->status == TID_IN_TCON)621			tcon->status = TID_NEED_TCON;622		spin_unlock(&tcon->tc_lock);623	} else {624		spin_lock(&tcon->tc_lock);625		if (tcon->status == TID_IN_TCON)626			tcon->status = TID_GOOD;627		tcon->need_reconnect = false;628		spin_unlock(&tcon->tc_lock);629	}630 631	return rc;632}633