| .. | .. |
|---|
| 1 | +// SPDX-License-Identifier: GPL-2.0-or-later |
|---|
| 1 | 2 | /* |
|---|
| 2 | 3 | * Contains the CIFS DFS referral mounting routines used for handling |
|---|
| 3 | 4 | * traversal via DFS junction point |
|---|
| .. | .. |
|---|
| 6 | 7 | * Copyright (C) International Business Machines Corp., 2008 |
|---|
| 7 | 8 | * Author(s): Igor Mammedov (niallain@gmail.com) |
|---|
| 8 | 9 | * Steve French (sfrench@us.ibm.com) |
|---|
| 9 | | - * This program is free software; you can redistribute it and/or |
|---|
| 10 | | - * modify it under the terms of the GNU General Public License |
|---|
| 11 | | - * as published by the Free Software Foundation; either version |
|---|
| 12 | | - * 2 of the License, or (at your option) any later version. |
|---|
| 13 | 10 | */ |
|---|
| 14 | 11 | |
|---|
| 15 | 12 | #include <linux/dcache.h> |
|---|
| .. | .. |
|---|
| 25 | 22 | #include "dns_resolve.h" |
|---|
| 26 | 23 | #include "cifs_debug.h" |
|---|
| 27 | 24 | #include "cifs_unicode.h" |
|---|
| 25 | +#include "dfs_cache.h" |
|---|
| 28 | 26 | |
|---|
| 29 | 27 | static LIST_HEAD(cifs_dfs_automount_list); |
|---|
| 30 | 28 | |
|---|
| .. | .. |
|---|
| 122 | 120 | |
|---|
| 123 | 121 | |
|---|
| 124 | 122 | /** |
|---|
| 125 | | - * cifs_compose_mount_options - creates mount options for refferral |
|---|
| 123 | + * cifs_compose_mount_options - creates mount options for referral |
|---|
| 126 | 124 | * @sb_mountdata: parent/root DFS mount options (template) |
|---|
| 127 | 125 | * @fullpath: full path in UNC format |
|---|
| 128 | | - * @ref: server's referral |
|---|
| 129 | | - * @devname: pointer for saving device name |
|---|
| 126 | + * @ref: optional server's referral |
|---|
| 127 | + * @devname: optional pointer for saving device name |
|---|
| 130 | 128 | * |
|---|
| 131 | 129 | * creates mount options for submount based on template options sb_mountdata |
|---|
| 132 | 130 | * and replacing unc,ip,prefixpath options with ones we've got form ref_unc. |
|---|
| 133 | 131 | * |
|---|
| 134 | 132 | * Returns: pointer to new mount options or ERR_PTR. |
|---|
| 135 | | - * Caller is responcible for freeing retunrned value if it is not error. |
|---|
| 133 | + * Caller is responsible for freeing returned value if it is not error. |
|---|
| 136 | 134 | */ |
|---|
| 137 | 135 | char *cifs_compose_mount_options(const char *sb_mountdata, |
|---|
| 138 | 136 | const char *fullpath, |
|---|
| .. | .. |
|---|
| 140 | 138 | char **devname) |
|---|
| 141 | 139 | { |
|---|
| 142 | 140 | int rc; |
|---|
| 141 | + char *name; |
|---|
| 143 | 142 | char *mountdata = NULL; |
|---|
| 144 | 143 | const char *prepath = NULL; |
|---|
| 145 | 144 | int md_len; |
|---|
| .. | .. |
|---|
| 151 | 150 | if (sb_mountdata == NULL) |
|---|
| 152 | 151 | return ERR_PTR(-EINVAL); |
|---|
| 153 | 152 | |
|---|
| 154 | | - if (strlen(fullpath) - ref->path_consumed) { |
|---|
| 155 | | - prepath = fullpath + ref->path_consumed; |
|---|
| 156 | | - /* skip initial delimiter */ |
|---|
| 157 | | - if (*prepath == '/' || *prepath == '\\') |
|---|
| 158 | | - prepath++; |
|---|
| 153 | + if (ref) { |
|---|
| 154 | + if (WARN_ON_ONCE(!ref->node_name || ref->path_consumed < 0)) |
|---|
| 155 | + return ERR_PTR(-EINVAL); |
|---|
| 156 | + |
|---|
| 157 | + if (strlen(fullpath) - ref->path_consumed) { |
|---|
| 158 | + prepath = fullpath + ref->path_consumed; |
|---|
| 159 | + /* skip initial delimiter */ |
|---|
| 160 | + if (*prepath == '/' || *prepath == '\\') |
|---|
| 161 | + prepath++; |
|---|
| 162 | + } |
|---|
| 163 | + |
|---|
| 164 | + name = cifs_build_devname(ref->node_name, prepath); |
|---|
| 165 | + if (IS_ERR(name)) { |
|---|
| 166 | + rc = PTR_ERR(name); |
|---|
| 167 | + name = NULL; |
|---|
| 168 | + goto compose_mount_options_err; |
|---|
| 169 | + } |
|---|
| 170 | + } else { |
|---|
| 171 | + name = cifs_build_devname((char *)fullpath, NULL); |
|---|
| 172 | + if (IS_ERR(name)) { |
|---|
| 173 | + rc = PTR_ERR(name); |
|---|
| 174 | + name = NULL; |
|---|
| 175 | + goto compose_mount_options_err; |
|---|
| 176 | + } |
|---|
| 159 | 177 | } |
|---|
| 160 | 178 | |
|---|
| 161 | | - *devname = cifs_build_devname(ref->node_name, prepath); |
|---|
| 162 | | - if (IS_ERR(*devname)) { |
|---|
| 163 | | - rc = PTR_ERR(*devname); |
|---|
| 164 | | - *devname = NULL; |
|---|
| 165 | | - goto compose_mount_options_err; |
|---|
| 166 | | - } |
|---|
| 167 | | - |
|---|
| 168 | | - rc = dns_resolve_server_name_to_ip(*devname, &srvIP); |
|---|
| 179 | + rc = dns_resolve_server_name_to_ip(name, &srvIP); |
|---|
| 169 | 180 | if (rc < 0) { |
|---|
| 170 | 181 | cifs_dbg(FYI, "%s: Failed to resolve server part of %s to IP: %d\n", |
|---|
| 171 | | - __func__, *devname, rc); |
|---|
| 182 | + __func__, name, rc); |
|---|
| 172 | 183 | goto compose_mount_options_err; |
|---|
| 173 | 184 | } |
|---|
| 174 | 185 | |
|---|
| .. | .. |
|---|
| 224 | 235 | strcat(mountdata, "ip="); |
|---|
| 225 | 236 | strcat(mountdata, srvIP); |
|---|
| 226 | 237 | |
|---|
| 238 | + if (devname) |
|---|
| 239 | + *devname = name; |
|---|
| 240 | + else |
|---|
| 241 | + kfree(name); |
|---|
| 242 | + |
|---|
| 227 | 243 | /*cifs_dbg(FYI, "%s: parent mountdata: %s\n", __func__, sb_mountdata);*/ |
|---|
| 228 | 244 | /*cifs_dbg(FYI, "%s: submount mountdata: %s\n", __func__, mountdata );*/ |
|---|
| 229 | 245 | |
|---|
| .. | .. |
|---|
| 234 | 250 | compose_mount_options_err: |
|---|
| 235 | 251 | kfree(mountdata); |
|---|
| 236 | 252 | mountdata = ERR_PTR(rc); |
|---|
| 237 | | - kfree(*devname); |
|---|
| 238 | | - *devname = NULL; |
|---|
| 253 | + kfree(name); |
|---|
| 239 | 254 | goto compose_mount_options_out; |
|---|
| 240 | 255 | } |
|---|
| 241 | 256 | |
|---|
| 242 | 257 | /** |
|---|
| 243 | | - * cifs_dfs_do_refmount - mounts specified path using provided refferal |
|---|
| 258 | + * cifs_dfs_do_mount - mounts specified path using DFS full path |
|---|
| 259 | + * |
|---|
| 260 | + * Always pass down @fullpath to smb3_do_mount() so we can use the root server |
|---|
| 261 | + * to perform failover in case we failed to connect to the first target in the |
|---|
| 262 | + * referral. |
|---|
| 263 | + * |
|---|
| 244 | 264 | * @cifs_sb: parent/root superblock |
|---|
| 245 | 265 | * @fullpath: full path in UNC format |
|---|
| 246 | | - * @ref: server's referral |
|---|
| 247 | 266 | */ |
|---|
| 248 | | -static struct vfsmount *cifs_dfs_do_refmount(struct dentry *mntpt, |
|---|
| 249 | | - struct cifs_sb_info *cifs_sb, |
|---|
| 250 | | - const char *fullpath, const struct dfs_info3_param *ref) |
|---|
| 267 | +static struct vfsmount *cifs_dfs_do_mount(struct dentry *mntpt, |
|---|
| 268 | + struct cifs_sb_info *cifs_sb, |
|---|
| 269 | + const char *fullpath) |
|---|
| 251 | 270 | { |
|---|
| 252 | 271 | struct vfsmount *mnt; |
|---|
| 253 | 272 | char *mountdata; |
|---|
| 254 | | - char *devname = NULL; |
|---|
| 273 | + char *devname; |
|---|
| 274 | + |
|---|
| 275 | + devname = kstrndup(fullpath, strlen(fullpath), GFP_KERNEL); |
|---|
| 276 | + if (!devname) |
|---|
| 277 | + return ERR_PTR(-ENOMEM); |
|---|
| 278 | + |
|---|
| 279 | + convert_delimiter(devname, '/'); |
|---|
| 255 | 280 | |
|---|
| 256 | 281 | /* strip first '\' from fullpath */ |
|---|
| 257 | 282 | mountdata = cifs_compose_mount_options(cifs_sb->mountdata, |
|---|
| 258 | | - fullpath + 1, ref, &devname); |
|---|
| 259 | | - |
|---|
| 260 | | - if (IS_ERR(mountdata)) |
|---|
| 283 | + fullpath + 1, NULL, NULL); |
|---|
| 284 | + if (IS_ERR(mountdata)) { |
|---|
| 285 | + kfree(devname); |
|---|
| 261 | 286 | return (struct vfsmount *)mountdata; |
|---|
| 287 | + } |
|---|
| 262 | 288 | |
|---|
| 263 | 289 | mnt = vfs_submount(mntpt, &cifs_fs_type, devname, mountdata); |
|---|
| 264 | 290 | kfree(mountdata); |
|---|
| 265 | 291 | kfree(devname); |
|---|
| 266 | 292 | return mnt; |
|---|
| 267 | | - |
|---|
| 268 | | -} |
|---|
| 269 | | - |
|---|
| 270 | | -static void dump_referral(const struct dfs_info3_param *ref) |
|---|
| 271 | | -{ |
|---|
| 272 | | - cifs_dbg(FYI, "DFS: ref path: %s\n", ref->path_name); |
|---|
| 273 | | - cifs_dbg(FYI, "DFS: node path: %s\n", ref->node_name); |
|---|
| 274 | | - cifs_dbg(FYI, "DFS: fl: %d, srv_type: %d\n", |
|---|
| 275 | | - ref->flags, ref->server_type); |
|---|
| 276 | | - cifs_dbg(FYI, "DFS: ref_flags: %d, path_consumed: %d\n", |
|---|
| 277 | | - ref->ref_flag, ref->path_consumed); |
|---|
| 278 | 293 | } |
|---|
| 279 | 294 | |
|---|
| 280 | 295 | /* |
|---|
| .. | .. |
|---|
| 282 | 297 | */ |
|---|
| 283 | 298 | static struct vfsmount *cifs_dfs_do_automount(struct dentry *mntpt) |
|---|
| 284 | 299 | { |
|---|
| 285 | | - struct dfs_info3_param *referrals = NULL; |
|---|
| 286 | | - unsigned int num_referrals = 0; |
|---|
| 287 | 300 | struct cifs_sb_info *cifs_sb; |
|---|
| 288 | 301 | struct cifs_ses *ses; |
|---|
| 289 | | - char *full_path; |
|---|
| 302 | + struct cifs_tcon *tcon; |
|---|
| 303 | + char *full_path, *root_path; |
|---|
| 290 | 304 | unsigned int xid; |
|---|
| 291 | | - int i; |
|---|
| 292 | 305 | int rc; |
|---|
| 293 | 306 | struct vfsmount *mnt; |
|---|
| 294 | | - struct tcon_link *tlink; |
|---|
| 295 | 307 | |
|---|
| 296 | 308 | cifs_dbg(FYI, "in %s\n", __func__); |
|---|
| 297 | 309 | BUG_ON(IS_ROOT(mntpt)); |
|---|
| .. | .. |
|---|
| 304 | 316 | */ |
|---|
| 305 | 317 | mnt = ERR_PTR(-ENOMEM); |
|---|
| 306 | 318 | |
|---|
| 319 | + cifs_sb = CIFS_SB(mntpt->d_sb); |
|---|
| 320 | + if (cifs_sb->mnt_cifs_flags & CIFS_MOUNT_NO_DFS) { |
|---|
| 321 | + mnt = ERR_PTR(-EREMOTE); |
|---|
| 322 | + goto cdda_exit; |
|---|
| 323 | + } |
|---|
| 324 | + |
|---|
| 307 | 325 | /* always use tree name prefix */ |
|---|
| 308 | 326 | full_path = build_path_from_dentry_optional_prefix(mntpt, true); |
|---|
| 309 | 327 | if (full_path == NULL) |
|---|
| 310 | 328 | goto cdda_exit; |
|---|
| 311 | 329 | |
|---|
| 312 | | - cifs_sb = CIFS_SB(mntpt->d_sb); |
|---|
| 313 | | - tlink = cifs_sb_tlink(cifs_sb); |
|---|
| 314 | | - if (IS_ERR(tlink)) { |
|---|
| 315 | | - mnt = ERR_CAST(tlink); |
|---|
| 330 | + convert_delimiter(full_path, '\\'); |
|---|
| 331 | + |
|---|
| 332 | + cifs_dbg(FYI, "%s: full_path: %s\n", __func__, full_path); |
|---|
| 333 | + |
|---|
| 334 | + if (!cifs_sb_master_tlink(cifs_sb)) { |
|---|
| 335 | + cifs_dbg(FYI, "%s: master tlink is NULL\n", __func__); |
|---|
| 316 | 336 | goto free_full_path; |
|---|
| 317 | 337 | } |
|---|
| 318 | | - ses = tlink_tcon(tlink)->ses; |
|---|
| 319 | 338 | |
|---|
| 320 | | - xid = get_xid(); |
|---|
| 321 | | - rc = get_dfs_path(xid, ses, full_path + 1, cifs_sb->local_nls, |
|---|
| 322 | | - &num_referrals, &referrals, |
|---|
| 323 | | - cifs_remap(cifs_sb)); |
|---|
| 324 | | - free_xid(xid); |
|---|
| 325 | | - |
|---|
| 326 | | - cifs_put_tlink(tlink); |
|---|
| 327 | | - |
|---|
| 328 | | - mnt = ERR_PTR(-ENOENT); |
|---|
| 329 | | - for (i = 0; i < num_referrals; i++) { |
|---|
| 330 | | - int len; |
|---|
| 331 | | - dump_referral(referrals + i); |
|---|
| 332 | | - /* connect to a node */ |
|---|
| 333 | | - len = strlen(referrals[i].node_name); |
|---|
| 334 | | - if (len < 2) { |
|---|
| 335 | | - cifs_dbg(VFS, "%s: Net Address path too short: %s\n", |
|---|
| 336 | | - __func__, referrals[i].node_name); |
|---|
| 337 | | - mnt = ERR_PTR(-EINVAL); |
|---|
| 338 | | - break; |
|---|
| 339 | | - } |
|---|
| 340 | | - mnt = cifs_dfs_do_refmount(mntpt, cifs_sb, |
|---|
| 341 | | - full_path, referrals + i); |
|---|
| 342 | | - cifs_dbg(FYI, "%s: cifs_dfs_do_refmount:%s , mnt:%p\n", |
|---|
| 343 | | - __func__, referrals[i].node_name, mnt); |
|---|
| 344 | | - if (!IS_ERR(mnt)) |
|---|
| 345 | | - goto success; |
|---|
| 339 | + tcon = cifs_sb_master_tcon(cifs_sb); |
|---|
| 340 | + if (!tcon) { |
|---|
| 341 | + cifs_dbg(FYI, "%s: master tcon is NULL\n", __func__); |
|---|
| 342 | + goto free_full_path; |
|---|
| 346 | 343 | } |
|---|
| 347 | 344 | |
|---|
| 348 | | - /* no valid submounts were found; return error from get_dfs_path() by |
|---|
| 349 | | - * preference */ |
|---|
| 350 | | - if (rc != 0) |
|---|
| 351 | | - mnt = ERR_PTR(rc); |
|---|
| 345 | + root_path = kstrdup(tcon->treeName, GFP_KERNEL); |
|---|
| 346 | + if (!root_path) { |
|---|
| 347 | + mnt = ERR_PTR(-ENOMEM); |
|---|
| 348 | + goto free_full_path; |
|---|
| 349 | + } |
|---|
| 350 | + cifs_dbg(FYI, "%s: root path: %s\n", __func__, root_path); |
|---|
| 352 | 351 | |
|---|
| 353 | | -success: |
|---|
| 354 | | - free_dfs_info_array(referrals, num_referrals); |
|---|
| 352 | + ses = tcon->ses; |
|---|
| 353 | + xid = get_xid(); |
|---|
| 354 | + |
|---|
| 355 | + /* |
|---|
| 356 | + * If DFS root has been expired, then unconditionally fetch it again to |
|---|
| 357 | + * refresh DFS referral cache. |
|---|
| 358 | + */ |
|---|
| 359 | + rc = dfs_cache_find(xid, ses, cifs_sb->local_nls, cifs_remap(cifs_sb), |
|---|
| 360 | + root_path + 1, NULL, NULL); |
|---|
| 361 | + if (!rc) { |
|---|
| 362 | + rc = dfs_cache_find(xid, ses, cifs_sb->local_nls, |
|---|
| 363 | + cifs_remap(cifs_sb), full_path + 1, |
|---|
| 364 | + NULL, NULL); |
|---|
| 365 | + } |
|---|
| 366 | + |
|---|
| 367 | + free_xid(xid); |
|---|
| 368 | + |
|---|
| 369 | + if (rc) { |
|---|
| 370 | + mnt = ERR_PTR(rc); |
|---|
| 371 | + goto free_root_path; |
|---|
| 372 | + } |
|---|
| 373 | + /* |
|---|
| 374 | + * OK - we were able to get and cache a referral for @full_path. |
|---|
| 375 | + * |
|---|
| 376 | + * Now, pass it down to cifs_mount() and it will retry every available |
|---|
| 377 | + * node server in case of failures - no need to do it here. |
|---|
| 378 | + */ |
|---|
| 379 | + mnt = cifs_dfs_do_mount(mntpt, cifs_sb, full_path); |
|---|
| 380 | + cifs_dbg(FYI, "%s: cifs_dfs_do_mount:%s , mnt:%p\n", __func__, |
|---|
| 381 | + full_path + 1, mnt); |
|---|
| 382 | + |
|---|
| 383 | +free_root_path: |
|---|
| 384 | + kfree(root_path); |
|---|
| 355 | 385 | free_full_path: |
|---|
| 356 | 386 | kfree(full_path); |
|---|
| 357 | 387 | cdda_exit: |
|---|