Merge tag 'xfs-6.4-merge-1' of git://git.kernel.org/pub/scm/fs/xfs/xfs-linux
[linux-block.git] / fs / cifs / dfs.c
1 // SPDX-License-Identifier: GPL-2.0
2 /*
3  * Copyright (c) 2022 Paulo Alcantara <palcantara@suse.de>
4  */
5
6 #include <linux/namei.h>
7 #include "cifsproto.h"
8 #include "cifs_debug.h"
9 #include "dns_resolve.h"
10 #include "fs_context.h"
11 #include "dfs.h"
12
13 /**
14  * dfs_parse_target_referral - set fs context for dfs target referral
15  *
16  * @full_path: full path in UNC format.
17  * @ref: dfs referral pointer.
18  * @ctx: smb3 fs context pointer.
19  *
20  * Return zero if dfs referral was parsed correctly, otherwise non-zero.
21  */
22 int dfs_parse_target_referral(const char *full_path, const struct dfs_info3_param *ref,
23                               struct smb3_fs_context *ctx)
24 {
25         int rc;
26         const char *prepath = NULL;
27         char *path;
28
29         if (!full_path || !*full_path || !ref || !ctx)
30                 return -EINVAL;
31
32         if (WARN_ON_ONCE(!ref->node_name || ref->path_consumed < 0))
33                 return -EINVAL;
34
35         if (strlen(full_path) - ref->path_consumed) {
36                 prepath = full_path + ref->path_consumed;
37                 /* skip initial delimiter */
38                 if (*prepath == '/' || *prepath == '\\')
39                         prepath++;
40         }
41
42         path = cifs_build_devname(ref->node_name, prepath);
43         if (IS_ERR(path))
44                 return PTR_ERR(path);
45
46         rc = smb3_parse_devname(path, ctx);
47         if (rc)
48                 goto out;
49
50         rc = dns_resolve_server_name_to_ip(path, (struct sockaddr *)&ctx->dstaddr, NULL);
51
52 out:
53         kfree(path);
54         return rc;
55 }
56
57 /*
58  * cifs_build_path_to_root returns full path to root when we do not have an
59  * existing connection (tcon)
60  */
61 static char *build_unc_path_to_root(const struct smb3_fs_context *ctx,
62                                     const struct cifs_sb_info *cifs_sb, bool useppath)
63 {
64         char *full_path, *pos;
65         unsigned int pplen = useppath && ctx->prepath ? strlen(ctx->prepath) + 1 : 0;
66         unsigned int unc_len = strnlen(ctx->UNC, MAX_TREE_SIZE + 1);
67
68         if (unc_len > MAX_TREE_SIZE)
69                 return ERR_PTR(-EINVAL);
70
71         full_path = kmalloc(unc_len + pplen + 1, GFP_KERNEL);
72         if (full_path == NULL)
73                 return ERR_PTR(-ENOMEM);
74
75         memcpy(full_path, ctx->UNC, unc_len);
76         pos = full_path + unc_len;
77
78         if (pplen) {
79                 *pos = CIFS_DIR_SEP(cifs_sb);
80                 memcpy(pos + 1, ctx->prepath, pplen);
81                 pos += pplen;
82         }
83
84         *pos = '\0'; /* add trailing null */
85         convert_delimiter(full_path, CIFS_DIR_SEP(cifs_sb));
86         cifs_dbg(FYI, "%s: full_path=%s\n", __func__, full_path);
87         return full_path;
88 }
89
90 static int get_session(struct cifs_mount_ctx *mnt_ctx, const char *full_path)
91 {
92         struct smb3_fs_context *ctx = mnt_ctx->fs_ctx;
93         int rc;
94
95         ctx->leaf_fullpath = (char *)full_path;
96         rc = cifs_mount_get_session(mnt_ctx);
97         ctx->leaf_fullpath = NULL;
98
99         return rc;
100 }
101
102 static int get_root_smb_session(struct cifs_mount_ctx *mnt_ctx)
103 {
104         struct smb3_fs_context *ctx = mnt_ctx->fs_ctx;
105         struct dfs_root_ses *root_ses;
106         struct cifs_ses *ses = mnt_ctx->ses;
107
108         if (ses) {
109                 root_ses = kmalloc(sizeof(*root_ses), GFP_KERNEL);
110                 if (!root_ses)
111                         return -ENOMEM;
112
113                 INIT_LIST_HEAD(&root_ses->list);
114
115                 spin_lock(&cifs_tcp_ses_lock);
116                 ses->ses_count++;
117                 spin_unlock(&cifs_tcp_ses_lock);
118                 root_ses->ses = ses;
119                 list_add_tail(&root_ses->list, &mnt_ctx->dfs_ses_list);
120         }
121         ctx->dfs_root_ses = ses;
122         return 0;
123 }
124
125 static int get_dfs_conn(struct cifs_mount_ctx *mnt_ctx, const char *ref_path, const char *full_path,
126                         const struct dfs_cache_tgt_iterator *tit)
127 {
128         struct smb3_fs_context *ctx = mnt_ctx->fs_ctx;
129         struct dfs_info3_param ref = {};
130         bool is_refsrv = false;
131         int rc, rc2;
132
133         rc = dfs_cache_get_tgt_referral(ref_path + 1, tit, &ref);
134         if (rc)
135                 return rc;
136
137         rc = dfs_parse_target_referral(full_path + 1, &ref, ctx);
138         if (rc)
139                 goto out;
140
141         cifs_mount_put_conns(mnt_ctx);
142         rc = get_session(mnt_ctx, ref_path);
143         if (rc)
144                 goto out;
145
146         is_refsrv = !!(ref.flags & DFSREF_REFERRAL_SERVER);
147
148         rc = -EREMOTE;
149         if (ref.flags & DFSREF_STORAGE_SERVER) {
150                 rc = cifs_mount_get_tcon(mnt_ctx);
151                 if (rc)
152                         goto out;
153
154                 /* some servers may not advertise referral capability under ref.flags */
155                 is_refsrv |= is_tcon_dfs(mnt_ctx->tcon);
156
157                 rc = cifs_is_path_remote(mnt_ctx);
158         }
159
160         if (rc == -EREMOTE && is_refsrv) {
161                 rc2 = get_root_smb_session(mnt_ctx);
162                 if (rc2)
163                         rc = rc2;
164         }
165
166 out:
167         free_dfs_info_param(&ref);
168         return rc;
169 }
170
171 static int __dfs_mount_share(struct cifs_mount_ctx *mnt_ctx)
172 {
173         struct cifs_sb_info *cifs_sb = mnt_ctx->cifs_sb;
174         struct smb3_fs_context *ctx = mnt_ctx->fs_ctx;
175         char *ref_path = NULL, *full_path = NULL;
176         struct dfs_cache_tgt_iterator *tit;
177         struct TCP_Server_Info *server;
178         struct cifs_tcon *tcon;
179         char *origin_fullpath = NULL;
180         int num_links = 0;
181         int rc;
182
183         ref_path = dfs_get_path(cifs_sb, ctx->UNC);
184         if (IS_ERR(ref_path))
185                 return PTR_ERR(ref_path);
186
187         full_path = build_unc_path_to_root(ctx, cifs_sb, true);
188         if (IS_ERR(full_path)) {
189                 rc = PTR_ERR(full_path);
190                 full_path = NULL;
191                 goto out;
192         }
193
194         origin_fullpath = kstrdup(full_path, GFP_KERNEL);
195         if (!origin_fullpath) {
196                 rc = -ENOMEM;
197                 goto out;
198         }
199
200         do {
201                 struct dfs_cache_tgt_list tl = DFS_CACHE_TGT_LIST_INIT(tl);
202
203                 rc = dfs_get_referral(mnt_ctx, ref_path + 1, NULL, &tl);
204                 if (rc)
205                         break;
206
207                 tit = dfs_cache_get_tgt_iterator(&tl);
208                 if (!tit) {
209                         cifs_dbg(VFS, "%s: dfs referral (%s) with no targets\n", __func__,
210                                  ref_path + 1);
211                         rc = -ENOENT;
212                         dfs_cache_free_tgts(&tl);
213                         break;
214                 }
215
216                 do {
217                         rc = get_dfs_conn(mnt_ctx, ref_path, full_path, tit);
218                         if (!rc)
219                                 break;
220                         if (rc == -EREMOTE) {
221                                 if (++num_links > MAX_NESTED_LINKS) {
222                                         rc = -ELOOP;
223                                         break;
224                                 }
225                                 kfree(ref_path);
226                                 kfree(full_path);
227                                 ref_path = full_path = NULL;
228
229                                 full_path = build_unc_path_to_root(ctx, cifs_sb, true);
230                                 if (IS_ERR(full_path)) {
231                                         rc = PTR_ERR(full_path);
232                                         full_path = NULL;
233                                 } else {
234                                         ref_path = dfs_get_path(cifs_sb, full_path);
235                                         if (IS_ERR(ref_path)) {
236                                                 rc = PTR_ERR(ref_path);
237                                                 ref_path = NULL;
238                                         }
239                                 }
240                                 break;
241                         }
242                 } while ((tit = dfs_cache_get_next_tgt(&tl, tit)));
243                 dfs_cache_free_tgts(&tl);
244         } while (rc == -EREMOTE);
245
246         if (!rc) {
247                 server = mnt_ctx->server;
248                 tcon = mnt_ctx->tcon;
249
250                 mutex_lock(&server->refpath_lock);
251                 if (!server->origin_fullpath) {
252                         server->origin_fullpath = origin_fullpath;
253                         server->current_fullpath = server->leaf_fullpath;
254                         origin_fullpath = NULL;
255                 }
256                 mutex_unlock(&server->refpath_lock);
257
258                 if (list_empty(&tcon->dfs_ses_list)) {
259                         list_replace_init(&mnt_ctx->dfs_ses_list,
260                                           &tcon->dfs_ses_list);
261                 } else {
262                         dfs_put_root_smb_sessions(&mnt_ctx->dfs_ses_list);
263                 }
264         }
265
266 out:
267         kfree(origin_fullpath);
268         kfree(ref_path);
269         kfree(full_path);
270         return rc;
271 }
272
273 int dfs_mount_share(struct cifs_mount_ctx *mnt_ctx, bool *isdfs)
274 {
275         struct cifs_sb_info *cifs_sb = mnt_ctx->cifs_sb;
276         struct smb3_fs_context *ctx = mnt_ctx->fs_ctx;
277         int rc;
278
279         *isdfs = false;
280
281         rc = get_session(mnt_ctx, NULL);
282         if (rc)
283                 return rc;
284         ctx->dfs_root_ses = mnt_ctx->ses;
285         /*
286          * If called with 'nodfs' mount option, then skip DFS resolving.  Otherwise unconditionally
287          * try to get an DFS referral (even cached) to determine whether it is an DFS mount.
288          *
289          * Skip prefix path to provide support for DFS referrals from w2k8 servers which don't seem
290          * to respond with PATH_NOT_COVERED to requests that include the prefix.
291          */
292         if ((cifs_sb->mnt_cifs_flags & CIFS_MOUNT_NO_DFS) ||
293             dfs_get_referral(mnt_ctx, ctx->UNC + 1, NULL, NULL)) {
294                 rc = cifs_mount_get_tcon(mnt_ctx);
295                 if (rc)
296                         return rc;
297
298                 rc = cifs_is_path_remote(mnt_ctx);
299                 if (!rc || rc != -EREMOTE)
300                         return rc;
301         }
302
303         *isdfs = true;
304         rc = get_root_smb_session(mnt_ctx);
305         if (rc)
306                 return rc;
307
308         return __dfs_mount_share(mnt_ctx);
309 }
310
311 /* Update dfs referral path of superblock */
312 static int update_server_fullpath(struct TCP_Server_Info *server, struct cifs_sb_info *cifs_sb,
313                                   const char *target)
314 {
315         int rc = 0;
316         size_t len = strlen(target);
317         char *refpath, *npath;
318
319         if (unlikely(len < 2 || *target != '\\'))
320                 return -EINVAL;
321
322         if (target[1] == '\\') {
323                 len += 1;
324                 refpath = kmalloc(len, GFP_KERNEL);
325                 if (!refpath)
326                         return -ENOMEM;
327
328                 scnprintf(refpath, len, "%s", target);
329         } else {
330                 len += sizeof("\\");
331                 refpath = kmalloc(len, GFP_KERNEL);
332                 if (!refpath)
333                         return -ENOMEM;
334
335                 scnprintf(refpath, len, "\\%s", target);
336         }
337
338         npath = dfs_cache_canonical_path(refpath, cifs_sb->local_nls, cifs_remap(cifs_sb));
339         kfree(refpath);
340
341         if (IS_ERR(npath)) {
342                 rc = PTR_ERR(npath);
343         } else {
344                 mutex_lock(&server->refpath_lock);
345                 kfree(server->leaf_fullpath);
346                 server->leaf_fullpath = npath;
347                 mutex_unlock(&server->refpath_lock);
348                 server->current_fullpath = server->leaf_fullpath;
349         }
350         return rc;
351 }
352
353 static int target_share_matches_server(struct TCP_Server_Info *server, char *share,
354                                        bool *target_match)
355 {
356         int rc = 0;
357         const char *dfs_host;
358         size_t dfs_host_len;
359
360         *target_match = true;
361         extract_unc_hostname(share, &dfs_host, &dfs_host_len);
362
363         /* Check if hostnames or addresses match */
364         cifs_server_lock(server);
365         if (dfs_host_len != strlen(server->hostname) ||
366             strncasecmp(dfs_host, server->hostname, dfs_host_len)) {
367                 cifs_dbg(FYI, "%s: %.*s doesn't match %s\n", __func__,
368                          (int)dfs_host_len, dfs_host, server->hostname);
369                 rc = match_target_ip(server, dfs_host, dfs_host_len, target_match);
370                 if (rc)
371                         cifs_dbg(VFS, "%s: failed to match target ip: %d\n", __func__, rc);
372         }
373         cifs_server_unlock(server);
374         return rc;
375 }
376
377 static int __tree_connect_dfs_target(const unsigned int xid, struct cifs_tcon *tcon,
378                                      struct cifs_sb_info *cifs_sb, char *tree, bool islink,
379                                      struct dfs_cache_tgt_list *tl)
380 {
381         int rc;
382         struct TCP_Server_Info *server = tcon->ses->server;
383         const struct smb_version_operations *ops = server->ops;
384         struct cifs_ses *root_ses = CIFS_DFS_ROOT_SES(tcon->ses);
385         struct cifs_tcon *ipc = root_ses->tcon_ipc;
386         char *share = NULL, *prefix = NULL;
387         struct dfs_cache_tgt_iterator *tit;
388         bool target_match;
389
390         tit = dfs_cache_get_tgt_iterator(tl);
391         if (!tit) {
392                 rc = -ENOENT;
393                 goto out;
394         }
395
396         /* Try to tree connect to all dfs targets */
397         for (; tit; tit = dfs_cache_get_next_tgt(tl, tit)) {
398                 const char *target = dfs_cache_get_tgt_name(tit);
399                 struct dfs_cache_tgt_list ntl = DFS_CACHE_TGT_LIST_INIT(ntl);
400
401                 kfree(share);
402                 kfree(prefix);
403                 share = prefix = NULL;
404
405                 /* Check if share matches with tcp ses */
406                 rc = dfs_cache_get_tgt_share(server->current_fullpath + 1, tit, &share, &prefix);
407                 if (rc) {
408                         cifs_dbg(VFS, "%s: failed to parse target share: %d\n", __func__, rc);
409                         break;
410                 }
411
412                 rc = target_share_matches_server(server, share, &target_match);
413                 if (rc)
414                         break;
415                 if (!target_match) {
416                         rc = -EHOSTUNREACH;
417                         continue;
418                 }
419
420                 dfs_cache_noreq_update_tgthint(server->current_fullpath + 1, tit);
421
422                 if (ipc->need_reconnect) {
423                         scnprintf(tree, MAX_TREE_SIZE, "\\\\%s\\IPC$", server->hostname);
424                         rc = ops->tree_connect(xid, ipc->ses, tree, ipc, cifs_sb->local_nls);
425                         cifs_dbg(FYI, "%s: reconnect ipc: %d\n", __func__, rc);
426                 }
427
428                 scnprintf(tree, MAX_TREE_SIZE, "\\%s", share);
429                 if (!islink) {
430                         rc = ops->tree_connect(xid, tcon->ses, tree, tcon, cifs_sb->local_nls);
431                         break;
432                 }
433                 /*
434                  * If no dfs referrals were returned from link target, then just do a TREE_CONNECT
435                  * to it.  Otherwise, cache the dfs referral and then mark current tcp ses for
436                  * reconnect so either the demultiplex thread or the echo worker will reconnect to
437                  * newly resolved target.
438                  */
439                 if (dfs_cache_find(xid, root_ses, cifs_sb->local_nls, cifs_remap(cifs_sb), target,
440                                    NULL, &ntl)) {
441                         rc = ops->tree_connect(xid, tcon->ses, tree, tcon, cifs_sb->local_nls);
442                         if (rc)
443                                 continue;
444
445                         rc = cifs_update_super_prepath(cifs_sb, prefix);
446                 } else {
447                         /* Target is another dfs share */
448                         rc = update_server_fullpath(server, cifs_sb, target);
449                         dfs_cache_free_tgts(tl);
450
451                         if (!rc) {
452                                 rc = -EREMOTE;
453                                 list_replace_init(&ntl.tl_list, &tl->tl_list);
454                         } else
455                                 dfs_cache_free_tgts(&ntl);
456                 }
457                 break;
458         }
459
460 out:
461         kfree(share);
462         kfree(prefix);
463
464         return rc;
465 }
466
467 static int tree_connect_dfs_target(const unsigned int xid, struct cifs_tcon *tcon,
468                                    struct cifs_sb_info *cifs_sb, char *tree, bool islink,
469                                    struct dfs_cache_tgt_list *tl)
470 {
471         int rc;
472         int num_links = 0;
473         struct TCP_Server_Info *server = tcon->ses->server;
474         char *old_fullpath = server->leaf_fullpath;
475
476         do {
477                 rc = __tree_connect_dfs_target(xid, tcon, cifs_sb, tree, islink, tl);
478                 if (!rc || rc != -EREMOTE)
479                         break;
480         } while (rc = -ELOOP, ++num_links < MAX_NESTED_LINKS);
481         /*
482          * If we couldn't tree connect to any targets from last referral path, then
483          * retry it from newly resolved dfs referral.
484          */
485         if (rc && server->leaf_fullpath != old_fullpath)
486                 cifs_signal_cifsd_for_reconnect(server, true);
487
488         dfs_cache_free_tgts(tl);
489         return rc;
490 }
491
492 int cifs_tree_connect(const unsigned int xid, struct cifs_tcon *tcon, const struct nls_table *nlsc)
493 {
494         int rc;
495         struct TCP_Server_Info *server = tcon->ses->server;
496         const struct smb_version_operations *ops = server->ops;
497         struct super_block *sb = NULL;
498         struct cifs_sb_info *cifs_sb;
499         struct dfs_cache_tgt_list tl = DFS_CACHE_TGT_LIST_INIT(tl);
500         char *tree;
501         struct dfs_info3_param ref = {0};
502
503         /* only send once per connect */
504         spin_lock(&tcon->tc_lock);
505         if (tcon->status != TID_NEW &&
506             tcon->status != TID_NEED_TCON) {
507                 spin_unlock(&tcon->tc_lock);
508                 return -EHOSTDOWN;
509         }
510
511         if (tcon->status == TID_GOOD) {
512                 spin_unlock(&tcon->tc_lock);
513                 return 0;
514         }
515         tcon->status = TID_IN_TCON;
516         spin_unlock(&tcon->tc_lock);
517
518         tree = kzalloc(MAX_TREE_SIZE, GFP_KERNEL);
519         if (!tree) {
520                 rc = -ENOMEM;
521                 goto out;
522         }
523
524         if (tcon->ipc) {
525                 cifs_server_lock(server);
526                 scnprintf(tree, MAX_TREE_SIZE, "\\\\%s\\IPC$", server->hostname);
527                 cifs_server_unlock(server);
528                 rc = ops->tree_connect(xid, tcon->ses, tree, tcon, nlsc);
529                 goto out;
530         }
531
532         sb = cifs_get_tcp_super(server);
533         if (IS_ERR(sb)) {
534                 rc = PTR_ERR(sb);
535                 cifs_dbg(VFS, "%s: could not find superblock: %d\n", __func__, rc);
536                 goto out;
537         }
538
539         cifs_sb = CIFS_SB(sb);
540
541         /* If it is not dfs or there was no cached dfs referral, then reconnect to same share */
542         if (!server->current_fullpath ||
543             dfs_cache_noreq_find(server->current_fullpath + 1, &ref, &tl)) {
544                 rc = ops->tree_connect(xid, tcon->ses, tcon->tree_name, tcon, cifs_sb->local_nls);
545                 goto out;
546         }
547
548         rc = tree_connect_dfs_target(xid, tcon, cifs_sb, tree, ref.server_type == DFS_TYPE_LINK,
549                                      &tl);
550         free_dfs_info_param(&ref);
551
552 out:
553         kfree(tree);
554         cifs_put_tcp_super(sb);
555
556         if (rc) {
557                 spin_lock(&tcon->tc_lock);
558                 if (tcon->status == TID_IN_TCON)
559                         tcon->status = TID_NEED_TCON;
560                 spin_unlock(&tcon->tc_lock);
561         } else {
562                 spin_lock(&tcon->tc_lock);
563                 if (tcon->status == TID_IN_TCON)
564                         tcon->status = TID_GOOD;
565                 spin_unlock(&tcon->tc_lock);
566                 tcon->need_reconnect = false;
567         }
568
569         return rc;
570 }