1 // SPDX-License-Identifier: GPL-2.0-or-later 2 /* 3 * Contains mounting routines used for handling traversal via SMB junctions. 4 * 5 * Copyright (c) 2007 Igor Mammedov 6 * Copyright (C) International Business Machines Corp., 2008 7 * Author(s): Igor Mammedov (niallain@gmail.com) 8 * Steve French (sfrench@us.ibm.com) 9 * Copyright (c) 2023 Paulo Alcantara <palcantara@suse.de> 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 "cifs_debug.h" 23 #include "fs_context.h" 24 25 static LIST_HEAD(cifs_automount_list); 26 27 static void cifs_expire_automounts(struct work_struct *work); 28 static DECLARE_DELAYED_WORK(cifs_automount_task, 29 cifs_expire_automounts); 30 static int cifs_mountpoint_expiry_timeout = 500 * HZ; 31 32 static void cifs_expire_automounts(struct work_struct *work) 33 { 34 struct list_head *list = &cifs_automount_list; 35 36 mark_mounts_for_expiry(list); 37 if (!list_empty(list)) 38 schedule_delayed_work(&cifs_automount_task, 39 cifs_mountpoint_expiry_timeout); 40 } 41 42 void cifs_release_automount_timer(void) 43 { 44 if (WARN_ON(!list_empty(&cifs_automount_list))) 45 return; 46 cancel_delayed_work_sync(&cifs_automount_task); 47 } 48 49 /** 50 * cifs_build_devname - build a devicename from a UNC and optional prepath 51 * @nodename: pointer to UNC string 52 * @prepath: pointer to prefixpath (or NULL if there isn't one) 53 * 54 * Build a new cifs devicename after chasing a DFS referral. Allocate a buffer 55 * big enough to hold the final thing. Copy the UNC from the nodename, and 56 * concatenate the prepath onto the end of it if there is one. 57 * 58 * Returns pointer to the built string, or a ERR_PTR. Caller is responsible 59 * for freeing the returned string. 60 */ 61 char * 62 cifs_build_devname(char *nodename, const char *prepath) 63 { 64 size_t pplen; 65 size_t unclen; 66 char *dev; 67 char *pos; 68 69 /* skip over any preceding delimiters */ 70 nodename += strspn(nodename, "\\"); 71 if (!*nodename) 72 return ERR_PTR(-EINVAL); 73 74 /* get length of UNC and set pos to last char */ 75 unclen = strlen(nodename); 76 pos = nodename + unclen - 1; 77 78 /* trim off any trailing delimiters */ 79 while (*pos == '\\') { 80 --pos; 81 --unclen; 82 } 83 84 /* allocate a buffer: 85 * +2 for preceding "//" 86 * +1 for delimiter between UNC and prepath 87 * +1 for trailing NULL 88 */ 89 pplen = prepath ? strlen(prepath) : 0; 90 dev = kmalloc(2 + unclen + 1 + pplen + 1, GFP_KERNEL); 91 if (!dev) 92 return ERR_PTR(-ENOMEM); 93 94 pos = dev; 95 /* add the initial "//" */ 96 *pos = '/'; 97 ++pos; 98 *pos = '/'; 99 ++pos; 100 101 /* copy in the UNC portion from referral */ 102 memcpy(pos, nodename, unclen); 103 pos += unclen; 104 105 /* copy the prefixpath remainder (if there is one) */ 106 if (pplen) { 107 *pos = '/'; 108 ++pos; 109 memcpy(pos, prepath, pplen); 110 pos += pplen; 111 } 112 113 /* NULL terminator */ 114 *pos = '\0'; 115 116 convert_delimiter(dev, '/'); 117 return dev; 118 } 119 120 static bool is_dfs_mount(struct dentry *dentry) 121 { 122 struct cifs_sb_info *cifs_sb = CIFS_SB(dentry->d_sb); 123 struct cifs_tcon *tcon = cifs_sb_master_tcon(cifs_sb); 124 bool ret; 125 126 spin_lock(&tcon->tc_lock); 127 ret = !!tcon->origin_fullpath; 128 spin_unlock(&tcon->tc_lock); 129 return ret; 130 } 131 132 /* Return full path out of a dentry set for automount */ 133 static char *automount_fullpath(struct dentry *dentry, void *page) 134 { 135 struct cifs_sb_info *cifs_sb = CIFS_SB(dentry->d_sb); 136 struct cifs_tcon *tcon = cifs_sb_master_tcon(cifs_sb); 137 size_t len; 138 char *s; 139 140 spin_lock(&tcon->tc_lock); 141 if (!tcon->origin_fullpath) { 142 spin_unlock(&tcon->tc_lock); 143 return build_path_from_dentry_optional_prefix(dentry, 144 page, 145 true); 146 } 147 spin_unlock(&tcon->tc_lock); 148 149 s = dentry_path_raw(dentry, page, PATH_MAX); 150 if (IS_ERR(s)) 151 return s; 152 /* for root, we want "" */ 153 if (!s[1]) 154 s++; 155 156 spin_lock(&tcon->tc_lock); 157 len = strlen(tcon->origin_fullpath); 158 if (s < (char *)page + len) { 159 spin_unlock(&tcon->tc_lock); 160 return ERR_PTR(-ENAMETOOLONG); 161 } 162 163 s -= len; 164 memcpy(s, tcon->origin_fullpath, len); 165 spin_unlock(&tcon->tc_lock); 166 convert_delimiter(s, '/'); 167 168 return s; 169 } 170 171 static void fs_context_set_ids(struct smb3_fs_context *ctx) 172 { 173 kuid_t uid = current_fsuid(); 174 kgid_t gid = current_fsgid(); 175 176 if (ctx->multiuser) { 177 if (!ctx->uid_specified) 178 ctx->linux_uid = uid; 179 if (!ctx->gid_specified) 180 ctx->linux_gid = gid; 181 } 182 if (!ctx->cruid_specified) 183 ctx->cred_uid = uid; 184 } 185 186 /* 187 * Create a vfsmount that we can automount 188 */ 189 static struct vfsmount *cifs_do_automount(struct path *path) 190 { 191 int rc; 192 struct dentry *mntpt = path->dentry; 193 struct fs_context *fc; 194 void *page = NULL; 195 struct smb3_fs_context *ctx, *cur_ctx; 196 struct smb3_fs_context tmp; 197 char *full_path; 198 struct vfsmount *mnt; 199 200 if (IS_ROOT(mntpt)) 201 return ERR_PTR(-ESTALE); 202 203 cur_ctx = CIFS_SB(mntpt->d_sb)->ctx; 204 205 fc = fs_context_for_submount(path->mnt->mnt_sb->s_type, mntpt); 206 if (IS_ERR(fc)) 207 return ERR_CAST(fc); 208 209 ctx = smb3_fc2context(fc); 210 211 page = alloc_dentry_path(); 212 full_path = automount_fullpath(mntpt, page); 213 if (IS_ERR(full_path)) { 214 mnt = ERR_CAST(full_path); 215 goto out; 216 } 217 218 tmp = *cur_ctx; 219 tmp.source = NULL; 220 tmp.leaf_fullpath = NULL; 221 tmp.UNC = tmp.prepath = NULL; 222 tmp.dfs_root_ses = NULL; 223 fs_context_set_ids(&tmp); 224 225 rc = smb3_fs_context_dup(ctx, &tmp); 226 if (rc) { 227 mnt = ERR_PTR(rc); 228 goto out; 229 } 230 231 rc = smb3_parse_devname(full_path, ctx); 232 if (rc) { 233 mnt = ERR_PTR(rc); 234 goto out; 235 } 236 237 ctx->source = smb3_fs_context_fullpath(ctx, '/'); 238 if (IS_ERR(ctx->source)) { 239 mnt = ERR_CAST(ctx->source); 240 ctx->source = NULL; 241 goto out; 242 } 243 ctx->dfs_automount = is_dfs_mount(mntpt); 244 cifs_dbg(FYI, "%s: ctx: source=%s UNC=%s prepath=%s dfs_automount=%d\n", 245 __func__, ctx->source, ctx->UNC, ctx->prepath, ctx->dfs_automount); 246 247 mnt = fc_mount(fc); 248 out: 249 put_fs_context(fc); 250 free_dentry_path(page); 251 return mnt; 252 } 253 254 /* 255 * Attempt to automount the referral 256 */ 257 struct vfsmount *cifs_d_automount(struct path *path) 258 { 259 struct vfsmount *newmnt; 260 261 cifs_dbg(FYI, "%s: %pd\n", __func__, path->dentry); 262 263 newmnt = cifs_do_automount(path); 264 if (IS_ERR(newmnt)) { 265 cifs_dbg(FYI, "leaving %s [automount failed]\n" , __func__); 266 return newmnt; 267 } 268 269 mntget(newmnt); /* prevent immediate expiration */ 270 mnt_set_expiry(newmnt, &cifs_automount_list); 271 schedule_delayed_work(&cifs_automount_task, 272 cifs_mountpoint_expiry_timeout); 273 cifs_dbg(FYI, "leaving %s [ok]\n" , __func__); 274 return newmnt; 275 } 276 277 const struct inode_operations cifs_namespace_inode_operations = { 278 }; 279
Linux® is a registered trademark of Linus Torvalds in the United States and other countries.
TOMOYO® is a registered trademark of NTT DATA CORPORATION.