Merge tag 'batadv-net-for-davem-20190201' of git://git.open-mesh.org/linux-merge
[linux-2.6-block.git] / fs / cifs / cifs_dfs_ref.c
CommitLineData
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 30static LIST_HEAD(cifs_dfs_automount_list);
6d5ae0de 31
78d31a3a
IM
32static void cifs_dfs_expire_automounts(struct work_struct *work);
33static DECLARE_DELAYED_WORK(cifs_dfs_automount_task,
34 cifs_dfs_expire_automounts);
35static int cifs_dfs_mountpoint_expiry_timeout = 500 * HZ;
36
37static 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 47void 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
65static char *
66cifs_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
138char *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
235compose_mount_options_out:
236 kfree(srvIP);
237 return mountdata;
c6fbba05
SF
238
239compose_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
252static 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 284static 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 */
297static 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
390free_dfs_ref:
391 free_dfs_info_param(&referral);
392free_root_path:
393 kfree(root_path);
01c64fea 394free_full_path:
6d5ae0de 395 kfree(full_path);
31c2659d 396cdda_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 */
404struct 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 424const struct inode_operations cifs_dfs_referral_inode_operations = {
6d5ae0de 425};