Linux Audio

Check our new training course

Loading...
Note: File does not exist in v5.9.
  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 */
 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
 50	rc = dns_resolve_server_name_to_ip(path, (struct sockaddr *)&ctx->dstaddr, NULL);
 51
 52out:
 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 */
 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	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
108static 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
119static 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
156out:
157	free_dfs_info_param(&ref);
158	return rc;
159}
160
161static 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
245out:
246	kfree(origin_fullpath);
247	kfree(ref_path);
248	kfree(full_path);
249	return rc;
250}
251
252int 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 */
289static 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
330static 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
354static 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
437out:
438	kfree(share);
439	kfree(prefix);
440
441	return rc;
442}
443
444static 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
469int 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
525out:
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}