| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478 |
- // SPDX-License-Identifier: GPL-2.0
- /*
- * Copyright (c) 2022 Paulo Alcantara <palcantara@suse.de>
- */
- #include "cifsproto.h"
- #include "cifs_debug.h"
- #include "dns_resolve.h"
- #include "fs_context.h"
- #include "dfs.h"
- /**
- * dfs_parse_target_referral - set fs context for dfs target referral
- *
- * @full_path: full path in UNC format.
- * @ref: dfs referral pointer.
- * @ctx: smb3 fs context pointer.
- *
- * Return zero if dfs referral was parsed correctly, otherwise non-zero.
- */
- int dfs_parse_target_referral(const char *full_path, const struct dfs_info3_param *ref,
- struct smb3_fs_context *ctx)
- {
- int rc;
- const char *prepath = NULL;
- char *path;
- if (!full_path || !*full_path || !ref || !ctx)
- return -EINVAL;
- if (WARN_ON_ONCE(!ref->node_name || ref->path_consumed < 0))
- return -EINVAL;
- if (strlen(full_path) - ref->path_consumed) {
- prepath = full_path + ref->path_consumed;
- /* skip initial delimiter */
- if (*prepath == '/' || *prepath == '\\')
- prepath++;
- }
- path = cifs_build_devname(ref->node_name, prepath);
- if (IS_ERR(path))
- return PTR_ERR(path);
- rc = smb3_parse_devname(path, ctx);
- if (rc)
- goto out;
- rc = dns_resolve_server_name_to_ip(path, (struct sockaddr *)&ctx->dstaddr, NULL);
- out:
- kfree(path);
- return rc;
- }
- static int get_session(struct cifs_mount_ctx *mnt_ctx, const char *full_path)
- {
- struct smb3_fs_context *ctx = mnt_ctx->fs_ctx;
- int rc;
- ctx->leaf_fullpath = (char *)full_path;
- rc = cifs_mount_get_session(mnt_ctx);
- ctx->leaf_fullpath = NULL;
- return rc;
- }
- /*
- * Get an active reference of @ses so that next call to cifs_put_tcon() won't
- * release it as any new DFS referrals must go through its IPC tcon.
- */
- static void set_root_smb_session(struct cifs_mount_ctx *mnt_ctx)
- {
- struct smb3_fs_context *ctx = mnt_ctx->fs_ctx;
- struct cifs_ses *ses = mnt_ctx->ses;
- if (ses) {
- spin_lock(&cifs_tcp_ses_lock);
- cifs_smb_ses_inc_refcount(ses);
- spin_unlock(&cifs_tcp_ses_lock);
- }
- ctx->dfs_root_ses = ses;
- }
- static inline int parse_dfs_target(struct smb3_fs_context *ctx,
- struct dfs_ref_walk *rw,
- struct dfs_info3_param *tgt)
- {
- int rc;
- const char *fpath = ref_walk_fpath(rw) + 1;
- rc = ref_walk_get_tgt(rw, tgt);
- if (!rc)
- rc = dfs_parse_target_referral(fpath, tgt, ctx);
- return rc;
- }
- static int setup_dfs_ref(struct cifs_mount_ctx *mnt_ctx,
- struct dfs_info3_param *tgt,
- struct dfs_ref_walk *rw)
- {
- struct smb3_fs_context *ctx = mnt_ctx->fs_ctx;
- struct cifs_sb_info *cifs_sb = mnt_ctx->cifs_sb;
- char *ref_path, *full_path;
- int rc;
- full_path = smb3_fs_context_fullpath(ctx, CIFS_DIR_SEP(cifs_sb));
- if (IS_ERR(full_path))
- return PTR_ERR(full_path);
- if (!tgt || (tgt->server_type == DFS_TYPE_LINK &&
- DFS_INTERLINK(tgt->flags)))
- ref_path = dfs_get_path(cifs_sb, ctx->UNC);
- else
- ref_path = dfs_get_path(cifs_sb, full_path);
- if (IS_ERR(ref_path)) {
- rc = PTR_ERR(ref_path);
- kfree(full_path);
- return rc;
- }
- ref_walk_path(rw) = ref_path;
- ref_walk_fpath(rw) = full_path;
- ref_walk_ses(rw) = ctx->dfs_root_ses;
- return 0;
- }
- static int __dfs_referral_walk(struct cifs_mount_ctx *mnt_ctx,
- struct dfs_ref_walk *rw)
- {
- struct smb3_fs_context *ctx = mnt_ctx->fs_ctx;
- struct dfs_info3_param tgt = {};
- int rc = -ENOENT;
- again:
- do {
- ctx->dfs_root_ses = ref_walk_ses(rw);
- if (ref_walk_empty(rw)) {
- rc = dfs_get_referral(mnt_ctx, ref_walk_path(rw) + 1,
- NULL, ref_walk_tl(rw));
- if (rc) {
- rc = cifs_mount_get_tcon(mnt_ctx);
- if (!rc)
- rc = cifs_is_path_remote(mnt_ctx);
- continue;
- }
- if (!ref_walk_num_tgts(rw)) {
- rc = -ENOENT;
- continue;
- }
- }
- while (ref_walk_next_tgt(rw)) {
- rc = parse_dfs_target(ctx, rw, &tgt);
- if (rc)
- continue;
- cifs_mount_put_conns(mnt_ctx);
- rc = get_session(mnt_ctx, ref_walk_path(rw));
- if (rc)
- continue;
- ref_walk_set_tgt_hint(rw);
- if (tgt.flags & DFSREF_STORAGE_SERVER) {
- rc = cifs_mount_get_tcon(mnt_ctx);
- if (!rc)
- rc = cifs_is_path_remote(mnt_ctx);
- if (!rc)
- break;
- if (rc != -EREMOTE)
- continue;
- }
- set_root_smb_session(mnt_ctx);
- rc = ref_walk_advance(rw);
- if (!rc) {
- rc = setup_dfs_ref(mnt_ctx, &tgt, rw);
- if (!rc) {
- rc = -EREMOTE;
- goto again;
- }
- }
- if (rc != -ELOOP)
- goto out;
- }
- } while (rc && ref_walk_descend(rw));
- out:
- free_dfs_info_param(&tgt);
- return rc;
- }
- static int dfs_referral_walk(struct cifs_mount_ctx *mnt_ctx,
- struct dfs_ref_walk **rw)
- {
- int rc;
- *rw = ref_walk_alloc();
- if (IS_ERR(*rw)) {
- rc = PTR_ERR(*rw);
- *rw = NULL;
- return rc;
- }
- ref_walk_init(*rw);
- rc = setup_dfs_ref(mnt_ctx, NULL, *rw);
- if (!rc)
- rc = __dfs_referral_walk(mnt_ctx, *rw);
- return rc;
- }
- static int __dfs_mount_share(struct cifs_mount_ctx *mnt_ctx)
- {
- struct cifs_sb_info *cifs_sb = mnt_ctx->cifs_sb;
- struct smb3_fs_context *ctx = mnt_ctx->fs_ctx;
- struct dfs_ref_walk *rw = NULL;
- struct cifs_tcon *tcon;
- char *origin_fullpath;
- int rc;
- origin_fullpath = dfs_get_path(cifs_sb, ctx->source);
- if (IS_ERR(origin_fullpath))
- return PTR_ERR(origin_fullpath);
- rc = dfs_referral_walk(mnt_ctx, &rw);
- if (!rc) {
- /*
- * Prevent superblock from being created with any missing
- * connections.
- */
- if (WARN_ON(!mnt_ctx->server))
- rc = -EHOSTDOWN;
- else if (WARN_ON(!mnt_ctx->ses))
- rc = -EACCES;
- else if (WARN_ON(!mnt_ctx->tcon))
- rc = -ENOENT;
- }
- if (rc)
- goto out;
- tcon = mnt_ctx->tcon;
- spin_lock(&tcon->tc_lock);
- tcon->origin_fullpath = origin_fullpath;
- origin_fullpath = NULL;
- ref_walk_set_tcon(rw, tcon);
- spin_unlock(&tcon->tc_lock);
- queue_delayed_work(dfscache_wq, &tcon->dfs_cache_work,
- dfs_cache_get_ttl() * HZ);
- out:
- kfree(origin_fullpath);
- ref_walk_free(rw);
- return rc;
- }
- /*
- * If @ctx->dfs_automount, then update @ctx->dstaddr earlier with the DFS root
- * server from where we'll start following any referrals. Otherwise rely on the
- * value provided by mount(2) as the user might not have dns_resolver key set up
- * and therefore failing to upcall to resolve UNC hostname under @ctx->source.
- */
- static int update_fs_context_dstaddr(struct smb3_fs_context *ctx)
- {
- struct sockaddr *addr = (struct sockaddr *)&ctx->dstaddr;
- int rc = 0;
- if (!ctx->nodfs && ctx->dfs_automount) {
- rc = dns_resolve_server_name_to_ip(ctx->source, addr, NULL);
- if (!rc)
- cifs_set_port(addr, ctx->port);
- ctx->dfs_automount = false;
- }
- return rc;
- }
- int dfs_mount_share(struct cifs_mount_ctx *mnt_ctx)
- {
- struct smb3_fs_context *ctx = mnt_ctx->fs_ctx;
- bool nodfs = ctx->nodfs;
- int rc;
- rc = update_fs_context_dstaddr(ctx);
- if (rc)
- return rc;
- rc = get_session(mnt_ctx, NULL);
- if (rc)
- return rc;
- /*
- * If called with 'nodfs' mount option, then skip DFS resolving. Otherwise unconditionally
- * try to get an DFS referral (even cached) to determine whether it is an DFS mount.
- *
- * Skip prefix path to provide support for DFS referrals from w2k8 servers which don't seem
- * to respond with PATH_NOT_COVERED to requests that include the prefix.
- */
- if (!nodfs) {
- rc = dfs_get_referral(mnt_ctx, ctx->UNC + 1, NULL, NULL);
- if (rc) {
- cifs_dbg(FYI, "%s: no dfs referral for %s: %d\n",
- __func__, ctx->UNC + 1, rc);
- cifs_dbg(FYI, "%s: assuming non-dfs mount...\n", __func__);
- nodfs = true;
- }
- }
- if (nodfs) {
- rc = cifs_mount_get_tcon(mnt_ctx);
- if (!rc)
- rc = cifs_is_path_remote(mnt_ctx);
- return rc;
- }
- if (!ctx->dfs_conn) {
- ctx->dfs_conn = true;
- cifs_mount_put_conns(mnt_ctx);
- rc = get_session(mnt_ctx, NULL);
- }
- if (!rc) {
- set_root_smb_session(mnt_ctx);
- rc = __dfs_mount_share(mnt_ctx);
- }
- return rc;
- }
- static int target_share_matches_server(struct TCP_Server_Info *server, char *share,
- bool *target_match)
- {
- int rc = 0;
- const char *dfs_host;
- size_t dfs_host_len;
- *target_match = true;
- extract_unc_hostname(share, &dfs_host, &dfs_host_len);
- /* Check if hostnames or addresses match */
- cifs_server_lock(server);
- if (dfs_host_len != strlen(server->hostname) ||
- strncasecmp(dfs_host, server->hostname, dfs_host_len)) {
- cifs_dbg(FYI, "%s: %.*s doesn't match %s\n", __func__,
- (int)dfs_host_len, dfs_host, server->hostname);
- rc = match_target_ip(server, dfs_host, dfs_host_len, target_match);
- if (rc)
- cifs_dbg(VFS, "%s: failed to match target ip: %d\n", __func__, rc);
- }
- cifs_server_unlock(server);
- return rc;
- }
- static int 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)
- {
- const struct smb_version_operations *ops = tcon->ses->server->ops;
- struct TCP_Server_Info *server = tcon->ses->server;
- struct dfs_cache_tgt_iterator *tit;
- char *share = NULL, *prefix = NULL;
- bool target_match;
- int rc = -ENOENT;
- /* Try to tree connect to all dfs targets */
- for (tit = dfs_cache_get_tgt_iterator(tl);
- tit; tit = dfs_cache_get_next_tgt(tl, tit)) {
- kfree(share);
- kfree(prefix);
- share = prefix = NULL;
- /* Check if share matches with tcp ses */
- rc = dfs_cache_get_tgt_share(server->leaf_fullpath + 1, tit, &share, &prefix);
- if (rc) {
- cifs_dbg(VFS, "%s: failed to parse target share: %d\n", __func__, rc);
- break;
- }
- rc = target_share_matches_server(server, share, &target_match);
- if (rc)
- break;
- if (!target_match) {
- rc = -EHOSTUNREACH;
- continue;
- }
- dfs_cache_noreq_update_tgthint(server->leaf_fullpath + 1, tit);
- scnprintf(tree, MAX_TREE_SIZE, "\\%s", share);
- rc = ops->tree_connect(xid, tcon->ses, tree,
- tcon, tcon->ses->local_nls);
- if (islink && !rc && cifs_sb)
- rc = cifs_update_super_prepath(cifs_sb, prefix);
- break;
- }
- kfree(share);
- kfree(prefix);
- dfs_cache_free_tgts(tl);
- return rc;
- }
- int cifs_tree_connect(const unsigned int xid, struct cifs_tcon *tcon, const struct nls_table *nlsc)
- {
- int rc;
- struct TCP_Server_Info *server = tcon->ses->server;
- const struct smb_version_operations *ops = server->ops;
- DFS_CACHE_TGT_LIST(tl);
- struct cifs_sb_info *cifs_sb = NULL;
- struct super_block *sb = NULL;
- struct dfs_info3_param ref = {0};
- char *tree;
- /* only send once per connect */
- spin_lock(&tcon->tc_lock);
- /* if tcon is marked for needing reconnect, update state */
- if (tcon->need_reconnect)
- tcon->status = TID_NEED_TCON;
- if (tcon->status == TID_GOOD) {
- spin_unlock(&tcon->tc_lock);
- return 0;
- }
- if (tcon->status != TID_NEW &&
- tcon->status != TID_NEED_TCON) {
- spin_unlock(&tcon->tc_lock);
- return -EHOSTDOWN;
- }
- tcon->status = TID_IN_TCON;
- spin_unlock(&tcon->tc_lock);
- tree = kzalloc(MAX_TREE_SIZE, GFP_KERNEL);
- if (!tree) {
- rc = -ENOMEM;
- goto out;
- }
- if (tcon->ipc) {
- cifs_server_lock(server);
- scnprintf(tree, MAX_TREE_SIZE, "\\\\%s\\IPC$", server->hostname);
- cifs_server_unlock(server);
- rc = ops->tree_connect(xid, tcon->ses, tree, tcon, nlsc);
- goto out;
- }
- sb = cifs_get_dfs_tcon_super(tcon);
- if (!IS_ERR(sb))
- cifs_sb = CIFS_SB(sb);
- /* Tree connect to last share in @tcon->tree_name if no DFS referral */
- if (!server->leaf_fullpath ||
- dfs_cache_noreq_find(server->leaf_fullpath + 1, &ref, &tl)) {
- rc = ops->tree_connect(xid, tcon->ses, tcon->tree_name,
- tcon, tcon->ses->local_nls);
- goto out;
- }
- rc = tree_connect_dfs_target(xid, tcon, cifs_sb, tree, ref.server_type == DFS_TYPE_LINK,
- &tl);
- free_dfs_info_param(&ref);
- out:
- kfree(tree);
- cifs_put_tcp_super(sb);
- if (rc) {
- spin_lock(&tcon->tc_lock);
- if (tcon->status == TID_IN_TCON)
- tcon->status = TID_NEED_TCON;
- spin_unlock(&tcon->tc_lock);
- } else {
- spin_lock(&tcon->tc_lock);
- if (tcon->status == TID_IN_TCON)
- tcon->status = TID_GOOD;
- tcon->need_reconnect = false;
- spin_unlock(&tcon->tc_lock);
- }
- return rc;
- }
|