Commit | Line | Data |
---|---|---|
6d5ae0de IM |
1 | /* |
2 | * Contains the CIFS DFS referral mounting routines used for handling | |
3 | * traversal via DFS junction point | |
4 | * | |
5 | * Copyright (c) 2007 Igor Mammedov | |
366781c1 | 6 | * Copyright (C) International Business Machines Corp., 2008 |
6d5ae0de | 7 | * Author(s): Igor Mammedov (niallain@gmail.com) |
366781c1 | 8 | * Steve French (sfrench@us.ibm.com) |
6d5ae0de IM |
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 | */ | |
14 | ||
15 | #include <linux/dcache.h> | |
16 | #include <linux/mount.h> | |
17 | #include <linux/namei.h> | |
5a0e3ad6 | 18 | #include <linux/slab.h> |
6d5ae0de IM |
19 | #include <linux/vfs.h> |
20 | #include <linux/fs.h> | |
166faf21 | 21 | #include <linux/inet.h> |
6d5ae0de IM |
22 | #include "cifsglob.h" |
23 | #include "cifsproto.h" | |
24 | #include "cifsfs.h" | |
25 | #include "dns_resolve.h" | |
26 | #include "cifs_debug.h" | |
bc8ebdc4 | 27 | #include "cifs_unicode.h" |
1c780228 | 28 | #include "dfs_cache.h" |
6d5ae0de | 29 | |
8d142137 | 30 | static LIST_HEAD(cifs_dfs_automount_list); |
6d5ae0de | 31 | |
78d31a3a IM |
32 | static void cifs_dfs_expire_automounts(struct work_struct *work); |
33 | static DECLARE_DELAYED_WORK(cifs_dfs_automount_task, | |
34 | cifs_dfs_expire_automounts); | |
35 | static int cifs_dfs_mountpoint_expiry_timeout = 500 * HZ; | |
36 | ||
37 | static void cifs_dfs_expire_automounts(struct work_struct *work) | |
38 | { | |
39 | struct list_head *list = &cifs_dfs_automount_list; | |
40 | ||
41 | mark_mounts_for_expiry(list); | |
42 | if (!list_empty(list)) | |
43 | schedule_delayed_work(&cifs_dfs_automount_task, | |
44 | cifs_dfs_mountpoint_expiry_timeout); | |
45 | } | |
6d5ae0de | 46 | |
78d31a3a | 47 | void cifs_dfs_release_automount_timer(void) |
6d5ae0de | 48 | { |
78d31a3a | 49 | BUG_ON(!list_empty(&cifs_dfs_automount_list)); |
3e24e132 | 50 | cancel_delayed_work_sync(&cifs_dfs_automount_task); |
6d5ae0de IM |
51 | } |
52 | ||
53 | /** | |
d9deef0a JL |
54 | * cifs_build_devname - build a devicename from a UNC and optional prepath |
55 | * @nodename: pointer to UNC string | |
56 | * @prepath: pointer to prefixpath (or NULL if there isn't one) | |
6d5ae0de | 57 | * |
d9deef0a JL |
58 | * Build a new cifs devicename after chasing a DFS referral. Allocate a buffer |
59 | * big enough to hold the final thing. Copy the UNC from the nodename, and | |
60 | * concatenate the prepath onto the end of it if there is one. | |
61 | * | |
62 | * Returns pointer to the built string, or a ERR_PTR. Caller is responsible | |
63 | * for freeing the returned string. | |
6d5ae0de | 64 | */ |
d9deef0a JL |
65 | static char * |
66 | cifs_build_devname(char *nodename, const char *prepath) | |
6d5ae0de | 67 | { |
d9deef0a JL |
68 | size_t pplen; |
69 | size_t unclen; | |
70 | char *dev; | |
71 | char *pos; | |
6d5ae0de | 72 | |
d9deef0a JL |
73 | /* skip over any preceding delimiters */ |
74 | nodename += strspn(nodename, "\\"); | |
75 | if (!*nodename) | |
7b91e266 | 76 | return ERR_PTR(-EINVAL); |
d9deef0a JL |
77 | |
78 | /* get length of UNC and set pos to last char */ | |
79 | unclen = strlen(nodename); | |
80 | pos = nodename + unclen - 1; | |
81 | ||
82 | /* trim off any trailing delimiters */ | |
83 | while (*pos == '\\') { | |
84 | --pos; | |
85 | --unclen; | |
6d5ae0de IM |
86 | } |
87 | ||
d9deef0a JL |
88 | /* allocate a buffer: |
89 | * +2 for preceding "//" | |
90 | * +1 for delimiter between UNC and prepath | |
91 | * +1 for trailing NULL | |
92 | */ | |
93 | pplen = prepath ? strlen(prepath) : 0; | |
94 | dev = kmalloc(2 + unclen + 1 + pplen + 1, GFP_KERNEL); | |
95 | if (!dev) | |
96 | return ERR_PTR(-ENOMEM); | |
97 | ||
98 | pos = dev; | |
99 | /* add the initial "//" */ | |
100 | *pos = '/'; | |
101 | ++pos; | |
102 | *pos = '/'; | |
103 | ++pos; | |
104 | ||
105 | /* copy in the UNC portion from referral */ | |
106 | memcpy(pos, nodename, unclen); | |
107 | pos += unclen; | |
108 | ||
109 | /* copy the prefixpath remainder (if there is one) */ | |
110 | if (pplen) { | |
111 | *pos = '/'; | |
112 | ++pos; | |
113 | memcpy(pos, prepath, pplen); | |
114 | pos += pplen; | |
6d5ae0de | 115 | } |
6d5ae0de | 116 | |
d9deef0a JL |
117 | /* NULL terminator */ |
118 | *pos = '\0'; | |
119 | ||
120 | convert_delimiter(dev, '/'); | |
121 | return dev; | |
6d5ae0de IM |
122 | } |
123 | ||
124 | ||
125 | /** | |
c6c00919 | 126 | * cifs_compose_mount_options - creates mount options for refferral |
6d5ae0de | 127 | * @sb_mountdata: parent/root DFS mount options (template) |
c6c00919 | 128 | * @fullpath: full path in UNC format |
2c55608f | 129 | * @ref: server's referral |
d9345e0a | 130 | * @devname: optional pointer for saving device name |
6d5ae0de IM |
131 | * |
132 | * creates mount options for submount based on template options sb_mountdata | |
133 | * and replacing unc,ip,prefixpath options with ones we've got form ref_unc. | |
134 | * | |
135 | * Returns: pointer to new mount options or ERR_PTR. | |
136 | * Caller is responcible for freeing retunrned value if it is not error. | |
137 | */ | |
c6c00919 SF |
138 | char *cifs_compose_mount_options(const char *sb_mountdata, |
139 | const char *fullpath, | |
2c55608f | 140 | const struct dfs_info3_param *ref, |
366781c1 | 141 | char **devname) |
6d5ae0de IM |
142 | { |
143 | int rc; | |
d9345e0a | 144 | char *name; |
c6fbba05 | 145 | char *mountdata = NULL; |
d9deef0a | 146 | const char *prepath = NULL; |
6d5ae0de IM |
147 | int md_len; |
148 | char *tkn_e; | |
149 | char *srvIP = NULL; | |
150 | char sep = ','; | |
151 | int off, noff; | |
152 | ||
153 | if (sb_mountdata == NULL) | |
154 | return ERR_PTR(-EINVAL); | |
155 | ||
11e31647 | 156 | if (strlen(fullpath) - ref->path_consumed) { |
d9deef0a | 157 | prepath = fullpath + ref->path_consumed; |
11e31647 SP |
158 | /* skip initial delimiter */ |
159 | if (*prepath == '/' || *prepath == '\\') | |
160 | prepath++; | |
161 | } | |
d9deef0a | 162 | |
d9345e0a PA |
163 | name = cifs_build_devname(ref->node_name, prepath); |
164 | if (IS_ERR(name)) { | |
165 | rc = PTR_ERR(name); | |
166 | name = NULL; | |
7b91e266 JL |
167 | goto compose_mount_options_err; |
168 | } | |
169 | ||
d9345e0a | 170 | rc = dns_resolve_server_name_to_ip(name, &srvIP); |
67b7626a | 171 | if (rc < 0) { |
f96637be | 172 | cifs_dbg(FYI, "%s: Failed to resolve server part of %s to IP: %d\n", |
d9345e0a | 173 | __func__, name, rc); |
c6fbba05 | 174 | goto compose_mount_options_err; |
6d5ae0de | 175 | } |
b8028983 | 176 | |
d9deef0a JL |
177 | /* |
178 | * In most cases, we'll be building a shorter string than the original, | |
179 | * but we do have to assume that the address in the ip= option may be | |
180 | * much longer than the original. Add the max length of an address | |
181 | * string to the length of the original string to allow for worst case. | |
2c55608f | 182 | */ |
d9deef0a | 183 | md_len = strlen(sb_mountdata) + INET6_ADDRSTRLEN; |
f34d69c3 | 184 | mountdata = kzalloc(md_len + sizeof("ip=") + 1, GFP_KERNEL); |
6d5ae0de | 185 | if (mountdata == NULL) { |
c6fbba05 SF |
186 | rc = -ENOMEM; |
187 | goto compose_mount_options_err; | |
6d5ae0de IM |
188 | } |
189 | ||
190 | /* copy all options except of unc,ip,prefixpath */ | |
191 | off = 0; | |
192 | if (strncmp(sb_mountdata, "sep=", 4) == 0) { | |
193 | sep = sb_mountdata[4]; | |
194 | strncpy(mountdata, sb_mountdata, 5); | |
195 | off += 5; | |
196 | } | |
2c55608f IM |
197 | |
198 | do { | |
199 | tkn_e = strchr(sb_mountdata + off, sep); | |
200 | if (tkn_e == NULL) | |
201 | noff = strlen(sb_mountdata + off); | |
202 | else | |
203 | noff = tkn_e - (sb_mountdata + off) + 1; | |
204 | ||
87e747cd | 205 | if (strncasecmp(sb_mountdata + off, "unc=", 4) == 0) { |
6d5ae0de IM |
206 | off += noff; |
207 | continue; | |
208 | } | |
87e747cd | 209 | if (strncasecmp(sb_mountdata + off, "ip=", 3) == 0) { |
6d5ae0de IM |
210 | off += noff; |
211 | continue; | |
212 | } | |
87e747cd | 213 | if (strncasecmp(sb_mountdata + off, "prefixpath=", 11) == 0) { |
6d5ae0de IM |
214 | off += noff; |
215 | continue; | |
216 | } | |
2c55608f | 217 | strncat(mountdata, sb_mountdata + off, noff); |
6d5ae0de | 218 | off += noff; |
2c55608f IM |
219 | } while (tkn_e); |
220 | strcat(mountdata, sb_mountdata + off); | |
6d5ae0de IM |
221 | mountdata[md_len] = '\0'; |
222 | ||
223 | /* copy new IP and ref share name */ | |
2c55608f IM |
224 | if (mountdata[strlen(mountdata) - 1] != sep) |
225 | strncat(mountdata, &sep, 1); | |
226 | strcat(mountdata, "ip="); | |
6d5ae0de | 227 | strcat(mountdata, srvIP); |
6d5ae0de | 228 | |
d9345e0a PA |
229 | if (devname) |
230 | *devname = name; | |
231 | ||
f96637be JP |
232 | /*cifs_dbg(FYI, "%s: parent mountdata: %s\n", __func__, sb_mountdata);*/ |
233 | /*cifs_dbg(FYI, "%s: submount mountdata: %s\n", __func__, mountdata );*/ | |
6d5ae0de IM |
234 | |
235 | compose_mount_options_out: | |
236 | kfree(srvIP); | |
237 | return mountdata; | |
c6fbba05 SF |
238 | |
239 | compose_mount_options_err: | |
240 | kfree(mountdata); | |
241 | mountdata = ERR_PTR(rc); | |
d9345e0a | 242 | kfree(name); |
c6fbba05 | 243 | goto compose_mount_options_out; |
6d5ae0de IM |
244 | } |
245 | ||
f67909cf SF |
246 | /** |
247 | * cifs_dfs_do_refmount - mounts specified path using provided refferal | |
248 | * @cifs_sb: parent/root superblock | |
249 | * @fullpath: full path in UNC format | |
250 | * @ref: server's referral | |
251 | */ | |
93faccbb EB |
252 | static struct vfsmount *cifs_dfs_do_refmount(struct dentry *mntpt, |
253 | struct cifs_sb_info *cifs_sb, | |
f67909cf | 254 | const char *fullpath, const struct dfs_info3_param *ref) |
6d5ae0de | 255 | { |
6d5ae0de IM |
256 | struct vfsmount *mnt; |
257 | char *mountdata; | |
4a367dc0 PA |
258 | char *devname; |
259 | ||
260 | /* | |
261 | * Always pass down the DFS full path to smb3_do_mount() so we | |
262 | * can use it later for failover. | |
263 | */ | |
264 | devname = kstrndup(fullpath, strlen(fullpath), GFP_KERNEL); | |
265 | if (!devname) | |
266 | return ERR_PTR(-ENOMEM); | |
267 | ||
268 | convert_delimiter(devname, '/'); | |
c6c00919 | 269 | |
f67909cf | 270 | /* strip first '\' from fullpath */ |
c6c00919 | 271 | mountdata = cifs_compose_mount_options(cifs_sb->mountdata, |
4a367dc0 PA |
272 | fullpath + 1, ref, NULL); |
273 | if (IS_ERR(mountdata)) { | |
274 | kfree(devname); | |
6d5ae0de | 275 | return (struct vfsmount *)mountdata; |
4a367dc0 | 276 | } |
6d5ae0de | 277 | |
93faccbb | 278 | mnt = vfs_submount(mntpt, &cifs_fs_type, devname, mountdata); |
6d5ae0de IM |
279 | kfree(mountdata); |
280 | kfree(devname); | |
281 | return mnt; | |
6d5ae0de IM |
282 | } |
283 | ||
366781c1 | 284 | static void dump_referral(const struct dfs_info3_param *ref) |
6d5ae0de | 285 | { |
f96637be JP |
286 | cifs_dbg(FYI, "DFS: ref path: %s\n", ref->path_name); |
287 | cifs_dbg(FYI, "DFS: node path: %s\n", ref->node_name); | |
288 | cifs_dbg(FYI, "DFS: fl: %hd, srv_type: %hd\n", | |
289 | ref->flags, ref->server_type); | |
290 | cifs_dbg(FYI, "DFS: ref_flags: %hd, path_consumed: %hd\n", | |
291 | ref->ref_flag, ref->path_consumed); | |
6d5ae0de IM |
292 | } |
293 | ||
01c64fea DH |
294 | /* |
295 | * Create a vfsmount that we can automount | |
296 | */ | |
297 | static struct vfsmount *cifs_dfs_do_automount(struct dentry *mntpt) | |
6d5ae0de | 298 | { |
1c780228 | 299 | struct dfs_info3_param referral = {0}; |
6d5ae0de | 300 | struct cifs_sb_info *cifs_sb; |
96daf2b0 | 301 | struct cifs_ses *ses; |
1c780228 PA |
302 | struct cifs_tcon *tcon; |
303 | char *full_path, *root_path; | |
6d5786a3 | 304 | unsigned int xid; |
1c780228 | 305 | int len; |
01c64fea DH |
306 | int rc; |
307 | struct vfsmount *mnt; | |
6d5ae0de | 308 | |
f96637be | 309 | cifs_dbg(FYI, "in %s\n", __func__); |
01c64fea | 310 | BUG_ON(IS_ROOT(mntpt)); |
6d5ae0de | 311 | |
c6fbba05 SF |
312 | /* |
313 | * The MSDFS spec states that paths in DFS referral requests and | |
314 | * responses must be prefixed by a single '\' character instead of | |
315 | * the double backslashes usually used in the UNC. This function | |
316 | * gives us the latter, so we must adjust the result. | |
317 | */ | |
01c64fea | 318 | mnt = ERR_PTR(-ENOMEM); |
268a635d | 319 | |
8393072b AA |
320 | cifs_sb = CIFS_SB(mntpt->d_sb); |
321 | if (cifs_sb->mnt_cifs_flags & CIFS_MOUNT_NO_DFS) { | |
322 | mnt = ERR_PTR(-EREMOTE); | |
323 | goto cdda_exit; | |
324 | } | |
325 | ||
268a635d AA |
326 | /* always use tree name prefix */ |
327 | full_path = build_path_from_dentry_optional_prefix(mntpt, true); | |
01c64fea | 328 | if (full_path == NULL) |
31c2659d | 329 | goto cdda_exit; |
6d5ae0de | 330 | |
1c780228 PA |
331 | cifs_dbg(FYI, "%s: full_path: %s\n", __func__, full_path); |
332 | ||
333 | if (!cifs_sb_master_tlink(cifs_sb)) { | |
334 | cifs_dbg(FYI, "%s: master tlink is NULL\n", __func__); | |
335 | goto free_full_path; | |
336 | } | |
337 | ||
338 | tcon = cifs_sb_master_tcon(cifs_sb); | |
339 | if (!tcon) { | |
340 | cifs_dbg(FYI, "%s: master tcon is NULL\n", __func__); | |
01c64fea | 341 | goto free_full_path; |
7ffec372 | 342 | } |
7ffec372 | 343 | |
1c780228 PA |
344 | root_path = kstrdup(tcon->treeName, GFP_KERNEL); |
345 | if (!root_path) { | |
346 | mnt = ERR_PTR(-ENOMEM); | |
347 | goto free_full_path; | |
348 | } | |
349 | cifs_dbg(FYI, "%s: root path: %s\n", __func__, root_path); | |
350 | ||
351 | ses = tcon->ses; | |
6d5786a3 | 352 | xid = get_xid(); |
6d5ae0de | 353 | |
1c780228 PA |
354 | /* |
355 | * If DFS root has been expired, then unconditionally fetch it again to | |
356 | * refresh DFS referral cache. | |
357 | */ | |
358 | rc = dfs_cache_find(xid, ses, cifs_sb->local_nls, cifs_remap(cifs_sb), | |
359 | root_path + 1, NULL, NULL); | |
360 | if (!rc) { | |
361 | rc = dfs_cache_find(xid, ses, cifs_sb->local_nls, | |
362 | cifs_remap(cifs_sb), full_path + 1, | |
363 | &referral, NULL); | |
6d5ae0de IM |
364 | } |
365 | ||
1c780228 PA |
366 | free_xid(xid); |
367 | ||
368 | if (rc) { | |
01c64fea | 369 | mnt = ERR_PTR(rc); |
1c780228 PA |
370 | goto free_root_path; |
371 | } | |
6d5ae0de | 372 | |
1c780228 PA |
373 | dump_referral(&referral); |
374 | ||
375 | len = strlen(referral.node_name); | |
376 | if (len < 2) { | |
377 | cifs_dbg(VFS, "%s: Net Address path too short: %s\n", | |
378 | __func__, referral.node_name); | |
379 | mnt = ERR_PTR(-EINVAL); | |
380 | goto free_dfs_ref; | |
381 | } | |
382 | /* | |
383 | * cifs_mount() will retry every available node server in case | |
384 | * of failures. | |
385 | */ | |
386 | mnt = cifs_dfs_do_refmount(mntpt, cifs_sb, full_path, &referral); | |
387 | cifs_dbg(FYI, "%s: cifs_dfs_do_refmount:%s , mnt:%p\n", __func__, | |
388 | referral.node_name, mnt); | |
389 | ||
390 | free_dfs_ref: | |
391 | free_dfs_info_param(&referral); | |
392 | free_root_path: | |
393 | kfree(root_path); | |
01c64fea | 394 | free_full_path: |
6d5ae0de | 395 | kfree(full_path); |
31c2659d | 396 | cdda_exit: |
f96637be | 397 | cifs_dbg(FYI, "leaving %s\n" , __func__); |
01c64fea DH |
398 | return mnt; |
399 | } | |
400 | ||
401 | /* | |
402 | * Attempt to automount the referral | |
403 | */ | |
404 | struct vfsmount *cifs_dfs_d_automount(struct path *path) | |
405 | { | |
406 | struct vfsmount *newmnt; | |
01c64fea | 407 | |
f96637be | 408 | cifs_dbg(FYI, "in %s\n", __func__); |
01c64fea DH |
409 | |
410 | newmnt = cifs_dfs_do_automount(path->dentry); | |
411 | if (IS_ERR(newmnt)) { | |
f96637be | 412 | cifs_dbg(FYI, "leaving %s [automount failed]\n" , __func__); |
01c64fea DH |
413 | return newmnt; |
414 | } | |
415 | ||
ea5b778a DH |
416 | mntget(newmnt); /* prevent immediate expiration */ |
417 | mnt_set_expiry(newmnt, &cifs_dfs_automount_list); | |
418 | schedule_delayed_work(&cifs_dfs_automount_task, | |
419 | cifs_dfs_mountpoint_expiry_timeout); | |
f96637be | 420 | cifs_dbg(FYI, "leaving %s [ok]\n" , __func__); |
ea5b778a | 421 | return newmnt; |
6d5ae0de IM |
422 | } |
423 | ||
6e1d5dcc | 424 | const struct inode_operations cifs_dfs_referral_inode_operations = { |
6d5ae0de | 425 | }; |