1// SPDX-License-Identifier: GPL-2.0-or-later 2/* 3 * Contains the CIFS DFS referral mounting routines used for handling 4 * traversal via DFS junction point 5 * 6 * Copyright (c) 2007 Igor Mammedov 7 * Copyright (C) International Business Machines Corp., 2008 8 * Author(s): Igor Mammedov (niallain@gmail.com) 9 * Steve French (sfrench@us.ibm.com) 10 */ 11 12#include <linux/dcache.h> 13#include <linux/mount.h> 14#include <linux/namei.h> 15#include <linux/slab.h> 16#include <linux/vfs.h> 17#include <linux/fs.h> 18#include <linux/inet.h> 19#include "cifsglob.h" 20#include "cifsproto.h" 21#include "cifsfs.h" 22#include "dns_resolve.h" 23#include "cifs_debug.h" 24#include "cifs_unicode.h" 25#include "dfs_cache.h" 26 27static LIST_HEAD(cifs_dfs_automount_list); 28 29static void cifs_dfs_expire_automounts(struct work_struct *work); 30static DECLARE_DELAYED_WORK(cifs_dfs_automount_task, 31 cifs_dfs_expire_automounts); 32static int cifs_dfs_mountpoint_expiry_timeout = 500 * HZ; 33 34static void cifs_dfs_expire_automounts(struct work_struct *work) 35{ 36 struct list_head *list = &cifs_dfs_automount_list; 37 38 mark_mounts_for_expiry(list); 39 if (!list_empty(list)) 40 schedule_delayed_work(&cifs_dfs_automount_task, 41 cifs_dfs_mountpoint_expiry_timeout); 42} 43 44void cifs_dfs_release_automount_timer(void) 45{ 46 BUG_ON(!list_empty(&cifs_dfs_automount_list)); 47 cancel_delayed_work_sync(&cifs_dfs_automount_task); 48} 49 50/** 51 * cifs_build_devname - build a devicename from a UNC and optional prepath 52 * @nodename: pointer to UNC string 53 * @prepath: pointer to prefixpath (or NULL if there isn't one) 54 * 55 * Build a new cifs devicename after chasing a DFS referral. Allocate a buffer 56 * big enough to hold the final thing. Copy the UNC from the nodename, and 57 * concatenate the prepath onto the end of it if there is one. 58 * 59 * Returns pointer to the built string, or a ERR_PTR. Caller is responsible 60 * for freeing the returned string. 61 */ 62static char * 63cifs_build_devname(char *nodename, const char *prepath) 64{ 65 size_t pplen; 66 size_t unclen; 67 char *dev; 68 char *pos; 69 70 /* skip over any preceding delimiters */ 71 nodename += strspn(nodename, "\\"); 72 if (!*nodename) 73 return ERR_PTR(-EINVAL); 74 75 /* get length of UNC and set pos to last char */ 76 unclen = strlen(nodename); 77 pos = nodename + unclen - 1; 78 79 /* trim off any trailing delimiters */ 80 while (*pos == '\\') { 81 --pos; 82 --unclen; 83 } 84 85 /* allocate a buffer: 86 * +2 for preceding "//" 87 * +1 for delimiter between UNC and prepath 88 * +1 for trailing NULL 89 */ 90 pplen = prepath ? strlen(prepath) : 0; 91 dev = kmalloc(2 + unclen + 1 + pplen + 1, GFP_KERNEL); 92 if (!dev) 93 return ERR_PTR(-ENOMEM); 94 95 pos = dev; 96 /* add the initial "//" */ 97 *pos = '/'; 98 ++pos; 99 *pos = '/'; 100 ++pos; 101 102 /* copy in the UNC portion from referral */ 103 memcpy(pos, nodename, unclen); 104 pos += unclen; 105 106 /* copy the prefixpath remainder (if there is one) */ 107 if (pplen) { 108 *pos = '/'; 109 ++pos; 110 memcpy(pos, prepath, pplen); 111 pos += pplen; 112 } 113 114 /* NULL terminator */ 115 *pos = '\0'; 116 117 convert_delimiter(dev, '/'); 118 return dev; 119} 120 121 122/** 123 * cifs_compose_mount_options - creates mount options for referral 124 * @sb_mountdata: parent/root DFS mount options (template) 125 * @fullpath: full path in UNC format 126 * @ref: optional server's referral 127 * @devname: optional pointer for saving device name 128 * 129 * creates mount options for submount based on template options sb_mountdata 130 * and replacing unc,ip,prefixpath options with ones we've got form ref_unc. 131 * 132 * Returns: pointer to new mount options or ERR_PTR. 133 * Caller is responsible for freeing returned value if it is not error. 134 */ 135char *cifs_compose_mount_options(const char *sb_mountdata, 136 const char *fullpath, 137 const struct dfs_info3_param *ref, 138 char **devname) 139{ 140 int rc; 141 char *name; 142 char *mountdata = NULL; 143 const char *prepath = NULL; 144 int md_len; 145 char *tkn_e; 146 char *srvIP = NULL; 147 char sep = ','; 148 int off, noff; 149 150 if (sb_mountdata == NULL) 151 return ERR_PTR(-EINVAL); 152 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 } 177 } 178 179 rc = dns_resolve_server_name_to_ip(name, &srvIP); 180 if (rc < 0) { 181 cifs_dbg(FYI, "%s: Failed to resolve server part of %s to IP: %d\n", 182 __func__, name, rc); 183 goto compose_mount_options_err; 184 } 185 186 /* 187 * In most cases, we'll be building a shorter string than the original, 188 * but we do have to assume that the address in the ip= option may be 189 * much longer than the original. Add the max length of an address 190 * string to the length of the original string to allow for worst case. 191 */ 192 md_len = strlen(sb_mountdata) + INET6_ADDRSTRLEN; 193 mountdata = kzalloc(md_len + sizeof("ip=") + 1, GFP_KERNEL); 194 if (mountdata == NULL) { 195 rc = -ENOMEM; 196 goto compose_mount_options_err; 197 } 198 199 /* copy all options except of unc,ip,prefixpath */ 200 off = 0; 201 if (strncmp(sb_mountdata, "sep=", 4) == 0) { 202 sep = sb_mountdata[4]; 203 strncpy(mountdata, sb_mountdata, 5); 204 off += 5; 205 } 206 207 do { 208 tkn_e = strchr(sb_mountdata + off, sep); 209 if (tkn_e == NULL) 210 noff = strlen(sb_mountdata + off); 211 else 212 noff = tkn_e - (sb_mountdata + off) + 1; 213 214 if (strncasecmp(sb_mountdata + off, "unc=", 4) == 0) { 215 off += noff; 216 continue; 217 } 218 if (strncasecmp(sb_mountdata + off, "ip=", 3) == 0) { 219 off += noff; 220 continue; 221 } 222 if (strncasecmp(sb_mountdata + off, "prefixpath=", 11) == 0) { 223 off += noff; 224 continue; 225 } 226 strncat(mountdata, sb_mountdata + off, noff); 227 off += noff; 228 } while (tkn_e); 229 strcat(mountdata, sb_mountdata + off); 230 mountdata[md_len] = '\0'; 231 232 /* copy new IP and ref share name */ 233 if (mountdata[strlen(mountdata) - 1] != sep) 234 strncat(mountdata, &sep, 1); 235 strcat(mountdata, "ip="); 236 strcat(mountdata, srvIP); 237 238 if (devname) 239 *devname = name; 240 else 241 kfree(name); 242 243 /*cifs_dbg(FYI, "%s: parent mountdata: %s\n", __func__, sb_mountdata);*/ 244 /*cifs_dbg(FYI, "%s: submount mountdata: %s\n", __func__, mountdata );*/ 245 246compose_mount_options_out: 247 kfree(srvIP); 248 return mountdata; 249 250compose_mount_options_err: 251 kfree(mountdata); 252 mountdata = ERR_PTR(rc); 253 kfree(name); 254 goto compose_mount_options_out; 255} 256 257/** 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 * 264 * @cifs_sb: parent/root superblock 265 * @fullpath: full path in UNC format 266 */ 267static struct vfsmount *cifs_dfs_do_mount(struct dentry *mntpt, 268 struct cifs_sb_info *cifs_sb, 269 const char *fullpath) 270{ 271 struct vfsmount *mnt; 272 char *mountdata; 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, '/'); 280 281 /* strip first '\' from fullpath */ 282 mountdata = cifs_compose_mount_options(cifs_sb->mountdata, 283 fullpath + 1, NULL, NULL); 284 if (IS_ERR(mountdata)) { 285 kfree(devname); 286 return (struct vfsmount *)mountdata; 287 } 288 289 mnt = vfs_submount(mntpt, &cifs_fs_type, devname, mountdata); 290 kfree(mountdata); 291 kfree(devname); 292 return mnt; 293} 294 295/* 296 * Create a vfsmount that we can automount 297 */ 298static struct vfsmount *cifs_dfs_do_automount(struct dentry *mntpt) 299{ 300 struct cifs_sb_info *cifs_sb; 301 struct cifs_ses *ses; 302 struct cifs_tcon *tcon; 303 char *full_path, *root_path; 304 unsigned int xid; 305 int rc; 306 struct vfsmount *mnt; 307 308 cifs_dbg(FYI, "in %s\n", __func__); 309 BUG_ON(IS_ROOT(mntpt)); 310 311 /* 312 * The MSDFS spec states that paths in DFS referral requests and 313 * responses must be prefixed by a single '\' character instead of 314 * the double backslashes usually used in the UNC. This function 315 * gives us the latter, so we must adjust the result. 316 */ 317 mnt = ERR_PTR(-ENOMEM); 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 325 /* always use tree name prefix */ 326 full_path = build_path_from_dentry_optional_prefix(mntpt, true); 327 if (full_path == NULL) 328 goto cdda_exit; 329 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__); 336 goto free_full_path; 337 } 338 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; 343 } 344 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); 351 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 383free_root_path: 384 kfree(root_path); 385free_full_path: 386 kfree(full_path); 387cdda_exit: 388 cifs_dbg(FYI, "leaving %s\n" , __func__); 389 return mnt; 390} 391 392/* 393 * Attempt to automount the referral 394 */ 395struct vfsmount *cifs_dfs_d_automount(struct path *path) 396{ 397 struct vfsmount *newmnt; 398 399 cifs_dbg(FYI, "in %s\n", __func__); 400 401 newmnt = cifs_dfs_do_automount(path->dentry); 402 if (IS_ERR(newmnt)) { 403 cifs_dbg(FYI, "leaving %s [automount failed]\n" , __func__); 404 return newmnt; 405 } 406 407 mntget(newmnt); /* prevent immediate expiration */ 408 mnt_set_expiry(newmnt, &cifs_dfs_automount_list); 409 schedule_delayed_work(&cifs_dfs_automount_task, 410 cifs_dfs_mountpoint_expiry_timeout); 411 cifs_dbg(FYI, "leaving %s [ok]\n" , __func__); 412 return newmnt; 413} 414 415const struct inode_operations cifs_dfs_referral_inode_operations = { 416}; 417