cifs_dfs_ref.c (9794B)
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#include "fs_context.h" 27 28static LIST_HEAD(cifs_dfs_automount_list); 29 30static void cifs_dfs_expire_automounts(struct work_struct *work); 31static DECLARE_DELAYED_WORK(cifs_dfs_automount_task, 32 cifs_dfs_expire_automounts); 33static int cifs_dfs_mountpoint_expiry_timeout = 500 * HZ; 34 35static void cifs_dfs_expire_automounts(struct work_struct *work) 36{ 37 struct list_head *list = &cifs_dfs_automount_list; 38 39 mark_mounts_for_expiry(list); 40 if (!list_empty(list)) 41 schedule_delayed_work(&cifs_dfs_automount_task, 42 cifs_dfs_mountpoint_expiry_timeout); 43} 44 45void cifs_dfs_release_automount_timer(void) 46{ 47 BUG_ON(!list_empty(&cifs_dfs_automount_list)); 48 cancel_delayed_work_sync(&cifs_dfs_automount_task); 49} 50 51/** 52 * cifs_build_devname - build a devicename from a UNC and optional prepath 53 * @nodename: pointer to UNC string 54 * @prepath: pointer to prefixpath (or NULL if there isn't one) 55 * 56 * Build a new cifs devicename after chasing a DFS referral. Allocate a buffer 57 * big enough to hold the final thing. Copy the UNC from the nodename, and 58 * concatenate the prepath onto the end of it if there is one. 59 * 60 * Returns pointer to the built string, or a ERR_PTR. Caller is responsible 61 * for freeing the returned string. 62 */ 63static char * 64cifs_build_devname(char *nodename, const char *prepath) 65{ 66 size_t pplen; 67 size_t unclen; 68 char *dev; 69 char *pos; 70 71 /* skip over any preceding delimiters */ 72 nodename += strspn(nodename, "\\"); 73 if (!*nodename) 74 return ERR_PTR(-EINVAL); 75 76 /* get length of UNC and set pos to last char */ 77 unclen = strlen(nodename); 78 pos = nodename + unclen - 1; 79 80 /* trim off any trailing delimiters */ 81 while (*pos == '\\') { 82 --pos; 83 --unclen; 84 } 85 86 /* allocate a buffer: 87 * +2 for preceding "//" 88 * +1 for delimiter between UNC and prepath 89 * +1 for trailing NULL 90 */ 91 pplen = prepath ? strlen(prepath) : 0; 92 dev = kmalloc(2 + unclen + 1 + pplen + 1, GFP_KERNEL); 93 if (!dev) 94 return ERR_PTR(-ENOMEM); 95 96 pos = dev; 97 /* add the initial "//" */ 98 *pos = '/'; 99 ++pos; 100 *pos = '/'; 101 ++pos; 102 103 /* copy in the UNC portion from referral */ 104 memcpy(pos, nodename, unclen); 105 pos += unclen; 106 107 /* copy the prefixpath remainder (if there is one) */ 108 if (pplen) { 109 *pos = '/'; 110 ++pos; 111 memcpy(pos, prepath, pplen); 112 pos += pplen; 113 } 114 115 /* NULL terminator */ 116 *pos = '\0'; 117 118 convert_delimiter(dev, '/'); 119 return dev; 120} 121 122 123/** 124 * cifs_compose_mount_options - creates mount options for referral 125 * @sb_mountdata: parent/root DFS mount options (template) 126 * @fullpath: full path in UNC format 127 * @ref: optional server's referral 128 * @devname: return the built cifs device name if passed pointer not NULL 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, NULL); 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, "cruid=", 6) == 0) { 215 off += noff; 216 continue; 217 } 218 if (strncasecmp(sb_mountdata + off, "unc=", 4) == 0) { 219 off += noff; 220 continue; 221 } 222 if (strncasecmp(sb_mountdata + off, "ip=", 3) == 0) { 223 off += noff; 224 continue; 225 } 226 if (strncasecmp(sb_mountdata + off, "prefixpath=", 11) == 0) { 227 off += noff; 228 continue; 229 } 230 strncat(mountdata, sb_mountdata + off, noff); 231 off += noff; 232 } while (tkn_e); 233 strcat(mountdata, sb_mountdata + off); 234 mountdata[md_len] = '\0'; 235 236 /* copy new IP and ref share name */ 237 if (mountdata[strlen(mountdata) - 1] != sep) 238 strncat(mountdata, &sep, 1); 239 strcat(mountdata, "ip="); 240 strcat(mountdata, srvIP); 241 242 if (devname) 243 *devname = name; 244 else 245 kfree(name); 246 247 /*cifs_dbg(FYI, "%s: parent mountdata: %s\n", __func__, sb_mountdata);*/ 248 /*cifs_dbg(FYI, "%s: submount mountdata: %s\n", __func__, mountdata );*/ 249 250compose_mount_options_out: 251 kfree(srvIP); 252 return mountdata; 253 254compose_mount_options_err: 255 kfree(mountdata); 256 mountdata = ERR_PTR(rc); 257 kfree(name); 258 goto compose_mount_options_out; 259} 260 261/** 262 * cifs_dfs_do_mount - mounts specified path using DFS full path 263 * 264 * Always pass down @fullpath to smb3_do_mount() so we can use the root server 265 * to perform failover in case we failed to connect to the first target in the 266 * referral. 267 * 268 * @mntpt: directory entry for the path we are trying to automount 269 * @cifs_sb: parent/root superblock 270 * @fullpath: full path in UNC format 271 */ 272static struct vfsmount *cifs_dfs_do_mount(struct dentry *mntpt, 273 struct cifs_sb_info *cifs_sb, 274 const char *fullpath) 275{ 276 struct vfsmount *mnt; 277 char *mountdata; 278 char *devname; 279 280 devname = kstrdup(fullpath, GFP_KERNEL); 281 if (!devname) 282 return ERR_PTR(-ENOMEM); 283 284 convert_delimiter(devname, '/'); 285 286 /* TODO: change to call fs_context_for_mount(), fill in context directly, call fc_mount */ 287 288 /* See afs_mntpt_do_automount in fs/afs/mntpt.c for an example */ 289 290 /* strip first '\' from fullpath */ 291 mountdata = cifs_compose_mount_options(cifs_sb->ctx->mount_options, 292 fullpath + 1, NULL, NULL); 293 if (IS_ERR(mountdata)) { 294 kfree(devname); 295 return (struct vfsmount *)mountdata; 296 } 297 298 mnt = vfs_submount(mntpt, &cifs_fs_type, devname, mountdata); 299 kfree(mountdata); 300 kfree(devname); 301 return mnt; 302} 303 304/* 305 * Create a vfsmount that we can automount 306 */ 307static struct vfsmount *cifs_dfs_do_automount(struct dentry *mntpt) 308{ 309 struct cifs_sb_info *cifs_sb; 310 void *page; 311 char *full_path; 312 struct vfsmount *mnt; 313 314 cifs_dbg(FYI, "in %s\n", __func__); 315 BUG_ON(IS_ROOT(mntpt)); 316 317 /* 318 * The MSDFS spec states that paths in DFS referral requests and 319 * responses must be prefixed by a single '\' character instead of 320 * the double backslashes usually used in the UNC. This function 321 * gives us the latter, so we must adjust the result. 322 */ 323 cifs_sb = CIFS_SB(mntpt->d_sb); 324 if (cifs_sb->mnt_cifs_flags & CIFS_MOUNT_NO_DFS) { 325 mnt = ERR_PTR(-EREMOTE); 326 goto cdda_exit; 327 } 328 329 page = alloc_dentry_path(); 330 /* always use tree name prefix */ 331 full_path = build_path_from_dentry_optional_prefix(mntpt, page, true); 332 if (IS_ERR(full_path)) { 333 mnt = ERR_CAST(full_path); 334 goto free_full_path; 335 } 336 337 convert_delimiter(full_path, '\\'); 338 cifs_dbg(FYI, "%s: full_path: %s\n", __func__, full_path); 339 340 mnt = cifs_dfs_do_mount(mntpt, cifs_sb, full_path); 341 cifs_dbg(FYI, "%s: cifs_dfs_do_mount:%s , mnt:%p\n", __func__, full_path + 1, mnt); 342 343free_full_path: 344 free_dentry_path(page); 345cdda_exit: 346 cifs_dbg(FYI, "leaving %s\n" , __func__); 347 return mnt; 348} 349 350/* 351 * Attempt to automount the referral 352 */ 353struct vfsmount *cifs_dfs_d_automount(struct path *path) 354{ 355 struct vfsmount *newmnt; 356 357 cifs_dbg(FYI, "in %s\n", __func__); 358 359 newmnt = cifs_dfs_do_automount(path->dentry); 360 if (IS_ERR(newmnt)) { 361 cifs_dbg(FYI, "leaving %s [automount failed]\n" , __func__); 362 return newmnt; 363 } 364 365 mntget(newmnt); /* prevent immediate expiration */ 366 mnt_set_expiry(newmnt, &cifs_dfs_automount_list); 367 schedule_delayed_work(&cifs_dfs_automount_task, 368 cifs_dfs_mountpoint_expiry_timeout); 369 cifs_dbg(FYI, "leaving %s [ok]\n" , __func__); 370 return newmnt; 371} 372 373const struct inode_operations cifs_dfs_referral_inode_operations = { 374};