File Index
Files and directories
Sign in
FILE

dfs.c

File preview with syntax highlighting.

linux-6.19.14/fs/smb/client/dfs.c C
  1// SPDX-License-Identifier: GPL-2.0
  2/*
  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#define DFS_DOM(ctx) (ctx->dfs_root_ses ? ctx->dfs_root_ses->dns_dom : NULL)
 13
 14/**
 15 * dfs_parse_target_referral - set fs context for dfs target referral
 16 *
 17 * @full_path: full path in UNC format.
 18 * @ref: dfs referral pointer.
 19 * @ctx: smb3 fs context pointer.
 20 *
 21 * Return zero if dfs referral was parsed correctly, otherwise non-zero.
 22 */
 23int dfs_parse_target_referral(const char *full_path, const struct dfs_info3_param *ref,
 24			      struct smb3_fs_context *ctx)
 25{
 26	int rc;
 27	const char *prepath = NULL;
 28	char *path;
 29
 30	if (!full_path || !*full_path || !ref || !ctx)
 31		return -EINVAL;
 32
 33	if (WARN_ON_ONCE(!ref->node_name || ref->path_consumed < 0))
 34		return -EINVAL;
 35
 36	if (strlen(full_path) - ref->path_consumed) {
 37		prepath = full_path + ref->path_consumed;
 38		/* skip initial delimiter */
 39		if (*prepath == '/' || *prepath == '\\')
 40			prepath++;
 41	}
 42
 43	path = cifs_build_devname(ref->node_name, prepath);
 44	if (IS_ERR(path))
 45		return PTR_ERR(path);
 46
 47	rc = smb3_parse_devname(path, ctx);
 48	if (rc)
 49		goto out;
 50
 51	rc = dns_resolve_unc(DFS_DOM(ctx), path,
 52			     (struct sockaddr *)&ctx->dstaddr);
 53out:
 54	kfree(path);
 55	return rc;
 56}
 57
 58static int get_session(struct cifs_mount_ctx *mnt_ctx, const char *full_path)
 59{
 60	struct smb3_fs_context *ctx = mnt_ctx->fs_ctx;
 61	int rc;
 62
 63	ctx->leaf_fullpath = (char *)full_path;
 64	ctx->dns_dom = DFS_DOM(ctx);
 65	rc = cifs_mount_get_session(mnt_ctx);
 66	ctx->leaf_fullpath = ctx->dns_dom = NULL;
 67
 68	return rc;
 69}
 70
 71/*
 72 * Get an active reference of @ses so that next call to cifs_put_tcon() won't
 73 * release it as any new DFS referrals must go through its IPC tcon.
 74 */
 75static void set_root_smb_session(struct cifs_mount_ctx *mnt_ctx)
 76{
 77	struct smb3_fs_context *ctx = mnt_ctx->fs_ctx;
 78	struct cifs_ses *ses = mnt_ctx->ses;
 79
 80	if (ses) {
 81		spin_lock(&cifs_tcp_ses_lock);
 82		cifs_smb_ses_inc_refcount(ses);
 83		spin_unlock(&cifs_tcp_ses_lock);
 84	}
 85	ctx->dfs_root_ses = ses;
 86}
 87
 88static inline int parse_dfs_target(struct smb3_fs_context *ctx,
 89				   struct dfs_ref_walk *rw,
 90				   struct dfs_info3_param *tgt)
 91{
 92	int rc;
 93	const char *fpath = ref_walk_fpath(rw) + 1;
 94
 95	rc = ref_walk_get_tgt(rw, tgt);
 96	if (!rc)
 97		rc = dfs_parse_target_referral(fpath, tgt, ctx);
 98	return rc;
 99}
100
101static int setup_dfs_ref(struct dfs_info3_param *tgt, struct dfs_ref_walk *rw)
102{
103	struct cifs_sb_info *cifs_sb = rw->mnt_ctx->cifs_sb;
104	struct smb3_fs_context *ctx = rw->mnt_ctx->fs_ctx;
105	char *ref_path, *full_path;
106	int rc;
107
108	set_root_smb_session(rw->mnt_ctx);
109	ref_walk_ses(rw) = ctx->dfs_root_ses;
110
111	full_path = smb3_fs_context_fullpath(ctx, CIFS_DIR_SEP(cifs_sb));
112	if (IS_ERR(full_path))
113		return PTR_ERR(full_path);
114
115	if (!tgt || (tgt->server_type == DFS_TYPE_LINK &&
116		     DFS_INTERLINK(tgt->flags)))
117		ref_path = dfs_get_path(cifs_sb, ctx->UNC);
118	else
119		ref_path = dfs_get_path(cifs_sb, full_path);
120	if (IS_ERR(ref_path)) {
121		rc = PTR_ERR(ref_path);
122		kfree(full_path);
123		return rc;
124	}
125	ref_walk_path(rw) = ref_path;
126	ref_walk_fpath(rw) = full_path;
127
128	return dfs_get_referral(rw->mnt_ctx,
129				ref_walk_path(rw) + 1,
130				ref_walk_tl(rw));
131}
132
133static int __dfs_referral_walk(struct dfs_ref_walk *rw)
134{
135	struct smb3_fs_context *ctx = rw->mnt_ctx->fs_ctx;
136	struct cifs_mount_ctx *mnt_ctx = rw->mnt_ctx;
137	struct dfs_info3_param tgt = {};
138	int rc = -ENOENT;
139
140again:
141	do {
142		ctx->dfs_root_ses = ref_walk_ses(rw);
143		while (ref_walk_next_tgt(rw)) {
144			rc = parse_dfs_target(ctx, rw, &tgt);
145			if (rc)
146				continue;
147
148			cifs_mount_put_conns(mnt_ctx);
149			rc = get_session(mnt_ctx, ref_walk_path(rw));
150			if (rc)
151				continue;
152
153			rc = cifs_mount_get_tcon(mnt_ctx);
154			if (rc) {
155				if (tgt.server_type == DFS_TYPE_LINK &&
156				    DFS_INTERLINK(tgt.flags))
157					rc = -EREMOTE;
158			} else {
159				rc = cifs_is_path_remote(mnt_ctx);
160				if (!rc) {
161					ref_walk_set_tgt_hint(rw);
162					break;
163				}
164			}
165			if (rc == -EREMOTE) {
166				rc = ref_walk_advance(rw);
167				if (!rc) {
168					rc = setup_dfs_ref(&tgt, rw);
169					if (rc)
170						break;
171					ref_walk_mark_end(rw);
172					goto again;
173				}
174			}
175		}
176	} while (rc && ref_walk_descend(rw));
177
178	free_dfs_info_param(&tgt);
179	return rc;
180}
181
182static int dfs_referral_walk(struct cifs_mount_ctx *mnt_ctx,
183			     struct dfs_ref_walk **rw)
184{
185	int rc;
186
187	*rw = ref_walk_alloc();
188	if (IS_ERR(*rw)) {
189		rc = PTR_ERR(*rw);
190		*rw = NULL;
191		return rc;
192	}
193
194	ref_walk_init(*rw, mnt_ctx);
195	rc = setup_dfs_ref(NULL, *rw);
196	if (!rc)
197		rc = __dfs_referral_walk(*rw);
198	return rc;
199}
200
201static int __dfs_mount_share(struct cifs_mount_ctx *mnt_ctx)
202{
203	struct cifs_sb_info *cifs_sb = mnt_ctx->cifs_sb;
204	struct smb3_fs_context *ctx = mnt_ctx->fs_ctx;
205	struct dfs_ref_walk *rw = NULL;
206	struct cifs_tcon *tcon;
207	char *origin_fullpath;
208	int rc;
209
210	origin_fullpath = dfs_get_path(cifs_sb, ctx->source);
211	if (IS_ERR(origin_fullpath))
212		return PTR_ERR(origin_fullpath);
213
214	rc = dfs_referral_walk(mnt_ctx, &rw);
215	if (!rc) {
216		/*
217		 * Prevent superblock from being created with any missing
218		 * connections.
219		 */
220		if (WARN_ON(!mnt_ctx->server))
221			rc = -EHOSTDOWN;
222		else if (WARN_ON(!mnt_ctx->ses))
223			rc = -EACCES;
224		else if (WARN_ON(!mnt_ctx->tcon))
225			rc = -ENOENT;
226	}
227	if (rc)
228		goto out;
229
230	tcon = mnt_ctx->tcon;
231	spin_lock(&tcon->tc_lock);
232	tcon->origin_fullpath = origin_fullpath;
233	origin_fullpath = NULL;
234	ref_walk_set_tcon(rw, tcon);
235	spin_unlock(&tcon->tc_lock);
236	queue_delayed_work(dfscache_wq, &tcon->dfs_cache_work,
237			   dfs_cache_get_ttl() * HZ);
238
239out:
240	kfree(origin_fullpath);
241	ref_walk_free(rw);
242	return rc;
243}
244
245/*
246 * If @ctx->dfs_automount, then update @ctx->dstaddr earlier with the DFS root
247 * server from where we'll start following any referrals.  Otherwise rely on the
248 * value provided by mount(2) as the user might not have dns_resolver key set up
249 * and therefore failing to upcall to resolve UNC hostname under @ctx->source.
250 */
251static int update_fs_context_dstaddr(struct smb3_fs_context *ctx)
252{
253	struct sockaddr *addr = (struct sockaddr *)&ctx->dstaddr;
254	int rc = 0;
255
256	if (!ctx->nodfs && ctx->dfs_automount) {
257		rc = dns_resolve_unc(NULL, ctx->source, addr);
258		if (!rc)
259			cifs_set_port(addr, ctx->port);
260		ctx->dfs_automount = false;
261	}
262	return rc;
263}
264
265int dfs_mount_share(struct cifs_mount_ctx *mnt_ctx)
266{
267	struct smb3_fs_context *ctx = mnt_ctx->fs_ctx;
268	bool nodfs = ctx->nodfs;
269	int rc;
270
271	rc = update_fs_context_dstaddr(ctx);
272	if (rc)
273		return rc;
274
275	rc = get_session(mnt_ctx, NULL);
276	if (rc)
277		return rc;
278
279	/*
280	 * If called with 'nodfs' mount option, then skip DFS resolving.  Otherwise unconditionally
281	 * try to get an DFS referral (even cached) to determine whether it is an DFS mount.
282	 *
283	 * Skip prefix path to provide support for DFS referrals from w2k8 servers which don't seem
284	 * to respond with PATH_NOT_COVERED to requests that include the prefix.
285	 */
286	if (!nodfs) {
287		rc = dfs_get_referral(mnt_ctx, ctx->UNC + 1, NULL);
288		if (rc) {
289			cifs_dbg(FYI, "%s: no dfs referral for %s: %d\n",
290				 __func__, ctx->UNC + 1, rc);
291			cifs_dbg(FYI, "%s: assuming non-dfs mount...\n", __func__);
292			nodfs = true;
293		}
294	}
295	if (nodfs) {
296		rc = cifs_mount_get_tcon(mnt_ctx);
297		if (!rc)
298			rc = cifs_is_path_remote(mnt_ctx);
299		return rc;
300	}
301
302	if (!ctx->dfs_conn) {
303		ctx->dfs_conn = true;
304		cifs_mount_put_conns(mnt_ctx);
305		rc = get_session(mnt_ctx, NULL);
306	}
307	if (!rc)
308		rc = __dfs_mount_share(mnt_ctx);
309	return rc;
310}
311
312static int target_share_matches_server(struct TCP_Server_Info *server, char *share,
313				       bool *target_match)
314{
315	int rc = 0;
316	const char *dfs_host;
317	size_t dfs_host_len;
318
319	*target_match = true;
320	extract_unc_hostname(share, &dfs_host, &dfs_host_len);
321
322	/* Check if hostnames or addresses match */
323	cifs_server_lock(server);
324	if (dfs_host_len != strlen(server->hostname) ||
325	    strncasecmp(dfs_host, server->hostname, dfs_host_len)) {
326		cifs_dbg(FYI, "%s: %.*s doesn't match %s\n", __func__,
327			 (int)dfs_host_len, dfs_host, server->hostname);
328		rc = match_target_ip(server, dfs_host, dfs_host_len, target_match);
329		if (rc)
330			cifs_dbg(VFS, "%s: failed to match target ip: %d\n", __func__, rc);
331	}
332	cifs_server_unlock(server);
333	return rc;
334}
335
336static int tree_connect_dfs_target(const unsigned int xid,
337				   struct cifs_tcon *tcon,
338				   struct cifs_sb_info *cifs_sb,
339				   char *tree, bool islink,
340				   struct dfs_cache_tgt_list *tl)
341{
342	const struct smb_version_operations *ops = tcon->ses->server->ops;
343	struct TCP_Server_Info *server = tcon->ses->server;
344	struct dfs_cache_tgt_iterator *tit;
345	char *share = NULL, *prefix = NULL;
346	bool target_match;
347	int rc = -ENOENT;
348
349	/* Try to tree connect to all dfs targets */
350	for (tit = dfs_cache_get_tgt_iterator(tl);
351	     tit; tit = dfs_cache_get_next_tgt(tl, tit)) {
352		kfree(share);
353		kfree(prefix);
354		share = prefix = NULL;
355
356		/* Check if share matches with tcp ses */
357		rc = dfs_cache_get_tgt_share(server->leaf_fullpath + 1, tit, &share, &prefix);
358		if (rc) {
359			cifs_dbg(VFS, "%s: failed to parse target share: %d\n", __func__, rc);
360			break;
361		}
362
363		rc = target_share_matches_server(server, share, &target_match);
364		if (rc)
365			break;
366		if (!target_match) {
367			rc = -EHOSTUNREACH;
368			continue;
369		}
370
371		dfs_cache_noreq_update_tgthint(server->leaf_fullpath + 1, tit);
372		scnprintf(tree, MAX_TREE_SIZE, "\\%s", share);
373		rc = ops->tree_connect(xid, tcon->ses, tree,
374				       tcon, tcon->ses->local_nls);
375		if (islink && !rc && cifs_sb)
376			rc = cifs_update_super_prepath(cifs_sb, prefix);
377		break;
378	}
379
380	kfree(share);
381	kfree(prefix);
382	dfs_cache_free_tgts(tl);
383	return rc;
384}
385
386int cifs_tree_connect(const unsigned int xid, struct cifs_tcon *tcon)
387{
388	int rc;
389	struct TCP_Server_Info *server = tcon->ses->server;
390	const struct smb_version_operations *ops = server->ops;
391	DFS_CACHE_TGT_LIST(tl);
392	struct cifs_sb_info *cifs_sb = NULL;
393	struct super_block *sb = NULL;
394	struct dfs_info3_param ref = {0};
395	char *tree;
396
397	/* only send once per connect */
398	spin_lock(&tcon->tc_lock);
399
400	/* if tcon is marked for needing reconnect, update state */
401	if (tcon->need_reconnect)
402		tcon->status = TID_NEED_TCON;
403
404	if (tcon->status == TID_GOOD) {
405		spin_unlock(&tcon->tc_lock);
406		return 0;
407	}
408
409	if (tcon->status != TID_NEW &&
410	    tcon->status != TID_NEED_TCON) {
411		spin_unlock(&tcon->tc_lock);
412		return -EHOSTDOWN;
413	}
414
415	tcon->status = TID_IN_TCON;
416	spin_unlock(&tcon->tc_lock);
417
418	tree = kzalloc(MAX_TREE_SIZE, GFP_KERNEL);
419	if (!tree) {
420		rc = -ENOMEM;
421		goto out;
422	}
423
424	if (tcon->ipc) {
425		cifs_server_lock(server);
426		scnprintf(tree, MAX_TREE_SIZE, "\\\\%s\\IPC$", server->hostname);
427		cifs_server_unlock(server);
428		rc = ops->tree_connect(xid, tcon->ses, tree,
429				       tcon, tcon->ses->local_nls);
430		goto out;
431	}
432
433	sb = cifs_get_dfs_tcon_super(tcon);
434	if (!IS_ERR(sb))
435		cifs_sb = CIFS_SB(sb);
436
437	/* Tree connect to last share in @tcon->tree_name if no DFS referral */
438	if (!server->leaf_fullpath ||
439	    dfs_cache_noreq_find(server->leaf_fullpath + 1, &ref, &tl)) {
440		rc = ops->tree_connect(xid, tcon->ses, tcon->tree_name,
441				       tcon, tcon->ses->local_nls);
442		goto out;
443	}
444
445	rc = tree_connect_dfs_target(xid, tcon, cifs_sb, tree, ref.server_type == DFS_TYPE_LINK,
446				     &tl);
447	free_dfs_info_param(&ref);
448
449out:
450	kfree(tree);
451	cifs_put_tcp_super(sb);
452
453	if (rc) {
454		spin_lock(&tcon->tc_lock);
455		if (tcon->status == TID_IN_TCON)
456			tcon->status = TID_NEED_TCON;
457		spin_unlock(&tcon->tc_lock);
458	} else {
459		spin_lock(&tcon->tc_lock);
460		if (tcon->status == TID_IN_TCON)
461			tcon->status = TID_GOOD;
462		tcon->need_reconnect = false;
463		spin_unlock(&tcon->tc_lock);
464	}
465
466	return rc;
467}
~/ Read-only file preview.