Merge tag 'drm-misc-next-fixes-2023-09-01' of git://anongit.freedesktop.org/drm/drm...
[platform/kernel/linux-rpi.git] / fs / smb / client / 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 static int get_session(struct cifs_mount_ctx *mnt_ctx, const char *full_path)
58 {
59         struct smb3_fs_context *ctx = mnt_ctx->fs_ctx;
60         int rc;
61
62         ctx->leaf_fullpath = (char *)full_path;
63         rc = cifs_mount_get_session(mnt_ctx);
64         ctx->leaf_fullpath = NULL;
65
66         return rc;
67 }
68
69 /*
70  * Track individual DFS referral servers used by new DFS mount.
71  *
72  * On success, their lifetime will be shared by final tcon (dfs_ses_list).
73  * Otherwise, they will be put by dfs_put_root_smb_sessions() in cifs_mount().
74  */
75 static int add_root_smb_session(struct cifs_mount_ctx *mnt_ctx)
76 {
77         struct smb3_fs_context *ctx = mnt_ctx->fs_ctx;
78         struct dfs_root_ses *root_ses;
79         struct cifs_ses *ses = mnt_ctx->ses;
80
81         if (ses) {
82                 root_ses = kmalloc(sizeof(*root_ses), GFP_KERNEL);
83                 if (!root_ses)
84                         return -ENOMEM;
85
86                 INIT_LIST_HEAD(&root_ses->list);
87
88                 spin_lock(&cifs_tcp_ses_lock);
89                 cifs_smb_ses_inc_refcount(ses);
90                 spin_unlock(&cifs_tcp_ses_lock);
91                 root_ses->ses = ses;
92                 list_add_tail(&root_ses->list, &mnt_ctx->dfs_ses_list);
93         }
94         /* Select new DFS referral server so that new referrals go through it */
95         ctx->dfs_root_ses = ses;
96         return 0;
97 }
98
99 static int get_dfs_conn(struct cifs_mount_ctx *mnt_ctx, const char *ref_path, const char *full_path,
100                         const struct dfs_cache_tgt_iterator *tit)
101 {
102         struct smb3_fs_context *ctx = mnt_ctx->fs_ctx;
103         struct dfs_info3_param ref = {};
104         bool is_refsrv;
105         int rc, rc2;
106
107         rc = dfs_cache_get_tgt_referral(ref_path + 1, tit, &ref);
108         if (rc)
109                 return rc;
110
111         rc = dfs_parse_target_referral(full_path + 1, &ref, ctx);
112         if (rc)
113                 goto out;
114
115         cifs_mount_put_conns(mnt_ctx);
116         rc = get_session(mnt_ctx, ref_path);
117         if (rc)
118                 goto out;
119
120         is_refsrv = !!(ref.flags & DFSREF_REFERRAL_SERVER);
121
122         rc = -EREMOTE;
123         if (ref.flags & DFSREF_STORAGE_SERVER) {
124                 rc = cifs_mount_get_tcon(mnt_ctx);
125                 if (rc)
126                         goto out;
127
128                 /* some servers may not advertise referral capability under ref.flags */
129                 is_refsrv |= is_tcon_dfs(mnt_ctx->tcon);
130
131                 rc = cifs_is_path_remote(mnt_ctx);
132         }
133
134         dfs_cache_noreq_update_tgthint(ref_path + 1, tit);
135
136         if (rc == -EREMOTE && is_refsrv) {
137                 rc2 = add_root_smb_session(mnt_ctx);
138                 if (rc2)
139                         rc = rc2;
140         }
141
142 out:
143         free_dfs_info_param(&ref);
144         return rc;
145 }
146
147 static int __dfs_mount_share(struct cifs_mount_ctx *mnt_ctx)
148 {
149         struct cifs_sb_info *cifs_sb = mnt_ctx->cifs_sb;
150         struct smb3_fs_context *ctx = mnt_ctx->fs_ctx;
151         char *ref_path = NULL, *full_path = NULL;
152         struct dfs_cache_tgt_iterator *tit;
153         struct cifs_tcon *tcon;
154         char *origin_fullpath = NULL;
155         char sep = CIFS_DIR_SEP(cifs_sb);
156         int num_links = 0;
157         int rc;
158
159         ref_path = dfs_get_path(cifs_sb, ctx->UNC);
160         if (IS_ERR(ref_path))
161                 return PTR_ERR(ref_path);
162
163         full_path = smb3_fs_context_fullpath(ctx, sep);
164         if (IS_ERR(full_path)) {
165                 rc = PTR_ERR(full_path);
166                 full_path = NULL;
167                 goto out;
168         }
169
170         origin_fullpath = kstrdup(full_path, GFP_KERNEL);
171         if (!origin_fullpath) {
172                 rc = -ENOMEM;
173                 goto out;
174         }
175
176         do {
177                 struct dfs_cache_tgt_list tl = DFS_CACHE_TGT_LIST_INIT(tl);
178
179                 rc = dfs_get_referral(mnt_ctx, ref_path + 1, NULL, &tl);
180                 if (rc) {
181                         rc = cifs_mount_get_tcon(mnt_ctx);
182                         if (!rc)
183                                 rc = cifs_is_path_remote(mnt_ctx);
184                         break;
185                 }
186
187                 tit = dfs_cache_get_tgt_iterator(&tl);
188                 if (!tit) {
189                         cifs_dbg(VFS, "%s: dfs referral (%s) with no targets\n", __func__,
190                                  ref_path + 1);
191                         rc = -ENOENT;
192                         dfs_cache_free_tgts(&tl);
193                         break;
194                 }
195
196                 do {
197                         rc = get_dfs_conn(mnt_ctx, ref_path, full_path, tit);
198                         if (!rc)
199                                 break;
200                         if (rc == -EREMOTE) {
201                                 if (++num_links > MAX_NESTED_LINKS) {
202                                         rc = -ELOOP;
203                                         break;
204                                 }
205                                 kfree(ref_path);
206                                 kfree(full_path);
207                                 ref_path = full_path = NULL;
208
209                                 full_path = smb3_fs_context_fullpath(ctx, sep);
210                                 if (IS_ERR(full_path)) {
211                                         rc = PTR_ERR(full_path);
212                                         full_path = NULL;
213                                 } else {
214                                         ref_path = dfs_get_path(cifs_sb, full_path);
215                                         if (IS_ERR(ref_path)) {
216                                                 rc = PTR_ERR(ref_path);
217                                                 ref_path = NULL;
218                                         }
219                                 }
220                                 break;
221                         }
222                 } while ((tit = dfs_cache_get_next_tgt(&tl, tit)));
223                 dfs_cache_free_tgts(&tl);
224         } while (rc == -EREMOTE);
225
226         if (!rc) {
227                 tcon = mnt_ctx->tcon;
228
229                 spin_lock(&tcon->tc_lock);
230                 if (!tcon->origin_fullpath) {
231                         tcon->origin_fullpath = origin_fullpath;
232                         origin_fullpath = NULL;
233                 }
234                 spin_unlock(&tcon->tc_lock);
235
236                 if (list_empty(&tcon->dfs_ses_list)) {
237                         list_replace_init(&mnt_ctx->dfs_ses_list,
238                                           &tcon->dfs_ses_list);
239                         queue_delayed_work(dfscache_wq, &tcon->dfs_cache_work,
240                                            dfs_cache_get_ttl() * HZ);
241                 } else {
242                         dfs_put_root_smb_sessions(&mnt_ctx->dfs_ses_list);
243                 }
244         }
245
246 out:
247         kfree(origin_fullpath);
248         kfree(ref_path);
249         kfree(full_path);
250         return rc;
251 }
252
253 int dfs_mount_share(struct cifs_mount_ctx *mnt_ctx, bool *isdfs)
254 {
255         struct smb3_fs_context *ctx = mnt_ctx->fs_ctx;
256         bool nodfs = ctx->nodfs;
257         int rc;
258
259         *isdfs = false;
260         rc = get_session(mnt_ctx, NULL);
261         if (rc)
262                 return rc;
263
264         ctx->dfs_root_ses = mnt_ctx->ses;
265         /*
266          * If called with 'nodfs' mount option, then skip DFS resolving.  Otherwise unconditionally
267          * try to get an DFS referral (even cached) to determine whether it is an DFS mount.
268          *
269          * Skip prefix path to provide support for DFS referrals from w2k8 servers which don't seem
270          * to respond with PATH_NOT_COVERED to requests that include the prefix.
271          */
272         if (!nodfs) {
273                 rc = dfs_get_referral(mnt_ctx, ctx->UNC + 1, NULL, NULL);
274                 if (rc) {
275                         cifs_dbg(FYI, "%s: no dfs referral for %s: %d\n",
276                                  __func__, ctx->UNC + 1, rc);
277                         cifs_dbg(FYI, "%s: assuming non-dfs mount...\n", __func__);
278                         nodfs = true;
279                 }
280         }
281         if (nodfs) {
282                 rc = cifs_mount_get_tcon(mnt_ctx);
283                 if (!rc)
284                         rc = cifs_is_path_remote(mnt_ctx);
285                 return rc;
286         }
287
288         *isdfs = true;
289         add_root_smb_session(mnt_ctx);
290         return __dfs_mount_share(mnt_ctx);
291 }
292
293 /* Update dfs referral path of superblock */
294 static int update_server_fullpath(struct TCP_Server_Info *server, struct cifs_sb_info *cifs_sb,
295                                   const char *target)
296 {
297         int rc = 0;
298         size_t len = strlen(target);
299         char *refpath, *npath;
300
301         if (unlikely(len < 2 || *target != '\\'))
302                 return -EINVAL;
303
304         if (target[1] == '\\') {
305                 len += 1;
306                 refpath = kmalloc(len, GFP_KERNEL);
307                 if (!refpath)
308                         return -ENOMEM;
309
310                 scnprintf(refpath, len, "%s", target);
311         } else {
312                 len += sizeof("\\");
313                 refpath = kmalloc(len, GFP_KERNEL);
314                 if (!refpath)
315                         return -ENOMEM;
316
317                 scnprintf(refpath, len, "\\%s", target);
318         }
319
320         npath = dfs_cache_canonical_path(refpath, cifs_sb->local_nls, cifs_remap(cifs_sb));
321         kfree(refpath);
322
323         if (IS_ERR(npath)) {
324                 rc = PTR_ERR(npath);
325         } else {
326                 mutex_lock(&server->refpath_lock);
327                 spin_lock(&server->srv_lock);
328                 kfree(server->leaf_fullpath);
329                 server->leaf_fullpath = npath;
330                 spin_unlock(&server->srv_lock);
331                 mutex_unlock(&server->refpath_lock);
332         }
333         return rc;
334 }
335
336 static int target_share_matches_server(struct TCP_Server_Info *server, char *share,
337                                        bool *target_match)
338 {
339         int rc = 0;
340         const char *dfs_host;
341         size_t dfs_host_len;
342
343         *target_match = true;
344         extract_unc_hostname(share, &dfs_host, &dfs_host_len);
345
346         /* Check if hostnames or addresses match */
347         cifs_server_lock(server);
348         if (dfs_host_len != strlen(server->hostname) ||
349             strncasecmp(dfs_host, server->hostname, dfs_host_len)) {
350                 cifs_dbg(FYI, "%s: %.*s doesn't match %s\n", __func__,
351                          (int)dfs_host_len, dfs_host, server->hostname);
352                 rc = match_target_ip(server, dfs_host, dfs_host_len, target_match);
353                 if (rc)
354                         cifs_dbg(VFS, "%s: failed to match target ip: %d\n", __func__, rc);
355         }
356         cifs_server_unlock(server);
357         return rc;
358 }
359
360 static void __tree_connect_ipc(const unsigned int xid, char *tree,
361                                struct cifs_sb_info *cifs_sb,
362                                struct cifs_ses *ses)
363 {
364         struct TCP_Server_Info *server = ses->server;
365         struct cifs_tcon *tcon = ses->tcon_ipc;
366         int rc;
367
368         spin_lock(&ses->ses_lock);
369         spin_lock(&ses->chan_lock);
370         if (cifs_chan_needs_reconnect(ses, server) ||
371             ses->ses_status != SES_GOOD) {
372                 spin_unlock(&ses->chan_lock);
373                 spin_unlock(&ses->ses_lock);
374                 cifs_server_dbg(FYI, "%s: skipping ipc reconnect due to disconnected ses\n",
375                                 __func__);
376                 return;
377         }
378         spin_unlock(&ses->chan_lock);
379         spin_unlock(&ses->ses_lock);
380
381         cifs_server_lock(server);
382         scnprintf(tree, MAX_TREE_SIZE, "\\\\%s\\IPC$", server->hostname);
383         cifs_server_unlock(server);
384
385         rc = server->ops->tree_connect(xid, ses, tree, tcon,
386                                        cifs_sb->local_nls);
387         cifs_server_dbg(FYI, "%s: tree_reconnect %s: %d\n", __func__, tree, rc);
388         spin_lock(&tcon->tc_lock);
389         if (rc) {
390                 tcon->status = TID_NEED_TCON;
391         } else {
392                 tcon->status = TID_GOOD;
393                 tcon->need_reconnect = false;
394         }
395         spin_unlock(&tcon->tc_lock);
396 }
397
398 static void tree_connect_ipc(const unsigned int xid, char *tree,
399                              struct cifs_sb_info *cifs_sb,
400                              struct cifs_tcon *tcon)
401 {
402         struct cifs_ses *ses = tcon->ses;
403
404         __tree_connect_ipc(xid, tree, cifs_sb, ses);
405         __tree_connect_ipc(xid, tree, cifs_sb, CIFS_DFS_ROOT_SES(ses));
406 }
407
408 static int __tree_connect_dfs_target(const unsigned int xid, struct cifs_tcon *tcon,
409                                      struct cifs_sb_info *cifs_sb, char *tree, bool islink,
410                                      struct dfs_cache_tgt_list *tl)
411 {
412         int rc;
413         struct TCP_Server_Info *server = tcon->ses->server;
414         const struct smb_version_operations *ops = server->ops;
415         struct cifs_ses *root_ses = CIFS_DFS_ROOT_SES(tcon->ses);
416         char *share = NULL, *prefix = NULL;
417         struct dfs_cache_tgt_iterator *tit;
418         bool target_match;
419
420         tit = dfs_cache_get_tgt_iterator(tl);
421         if (!tit) {
422                 rc = -ENOENT;
423                 goto out;
424         }
425
426         /* Try to tree connect to all dfs targets */
427         for (; tit; tit = dfs_cache_get_next_tgt(tl, tit)) {
428                 const char *target = dfs_cache_get_tgt_name(tit);
429                 struct dfs_cache_tgt_list ntl = DFS_CACHE_TGT_LIST_INIT(ntl);
430
431                 kfree(share);
432                 kfree(prefix);
433                 share = prefix = NULL;
434
435                 /* Check if share matches with tcp ses */
436                 rc = dfs_cache_get_tgt_share(server->leaf_fullpath + 1, tit, &share, &prefix);
437                 if (rc) {
438                         cifs_dbg(VFS, "%s: failed to parse target share: %d\n", __func__, rc);
439                         break;
440                 }
441
442                 rc = target_share_matches_server(server, share, &target_match);
443                 if (rc)
444                         break;
445                 if (!target_match) {
446                         rc = -EHOSTUNREACH;
447                         continue;
448                 }
449
450                 dfs_cache_noreq_update_tgthint(server->leaf_fullpath + 1, tit);
451                 tree_connect_ipc(xid, tree, cifs_sb, tcon);
452
453                 scnprintf(tree, MAX_TREE_SIZE, "\\%s", share);
454                 if (!islink) {
455                         rc = ops->tree_connect(xid, tcon->ses, tree, tcon, cifs_sb->local_nls);
456                         break;
457                 }
458
459                 /*
460                  * If no dfs referrals were returned from link target, then just do a TREE_CONNECT
461                  * to it.  Otherwise, cache the dfs referral and then mark current tcp ses for
462                  * reconnect so either the demultiplex thread or the echo worker will reconnect to
463                  * newly resolved target.
464                  */
465                 if (dfs_cache_find(xid, root_ses, cifs_sb->local_nls, cifs_remap(cifs_sb), target,
466                                    NULL, &ntl)) {
467                         rc = ops->tree_connect(xid, tcon->ses, tree, tcon, cifs_sb->local_nls);
468                         if (rc)
469                                 continue;
470
471                         rc = cifs_update_super_prepath(cifs_sb, prefix);
472                 } else {
473                         /* Target is another dfs share */
474                         rc = update_server_fullpath(server, cifs_sb, target);
475                         dfs_cache_free_tgts(tl);
476
477                         if (!rc) {
478                                 rc = -EREMOTE;
479                                 list_replace_init(&ntl.tl_list, &tl->tl_list);
480                         } else
481                                 dfs_cache_free_tgts(&ntl);
482                 }
483                 break;
484         }
485
486 out:
487         kfree(share);
488         kfree(prefix);
489
490         return rc;
491 }
492
493 static int tree_connect_dfs_target(const unsigned int xid, struct cifs_tcon *tcon,
494                                    struct cifs_sb_info *cifs_sb, char *tree, bool islink,
495                                    struct dfs_cache_tgt_list *tl)
496 {
497         int rc;
498         int num_links = 0;
499         struct TCP_Server_Info *server = tcon->ses->server;
500         char *old_fullpath = server->leaf_fullpath;
501
502         do {
503                 rc = __tree_connect_dfs_target(xid, tcon, cifs_sb, tree, islink, tl);
504                 if (!rc || rc != -EREMOTE)
505                         break;
506         } while (rc = -ELOOP, ++num_links < MAX_NESTED_LINKS);
507         /*
508          * If we couldn't tree connect to any targets from last referral path, then
509          * retry it from newly resolved dfs referral.
510          */
511         if (rc && server->leaf_fullpath != old_fullpath)
512                 cifs_signal_cifsd_for_reconnect(server, true);
513
514         dfs_cache_free_tgts(tl);
515         return rc;
516 }
517
518 int cifs_tree_connect(const unsigned int xid, struct cifs_tcon *tcon, const struct nls_table *nlsc)
519 {
520         int rc;
521         struct TCP_Server_Info *server = tcon->ses->server;
522         const struct smb_version_operations *ops = server->ops;
523         struct dfs_cache_tgt_list tl = DFS_CACHE_TGT_LIST_INIT(tl);
524         struct cifs_sb_info *cifs_sb = NULL;
525         struct super_block *sb = NULL;
526         struct dfs_info3_param ref = {0};
527         char *tree;
528
529         /* only send once per connect */
530         spin_lock(&tcon->tc_lock);
531         if (tcon->status == TID_GOOD) {
532                 spin_unlock(&tcon->tc_lock);
533                 return 0;
534         }
535
536         if (tcon->status != TID_NEW &&
537             tcon->status != TID_NEED_TCON) {
538                 spin_unlock(&tcon->tc_lock);
539                 return -EHOSTDOWN;
540         }
541
542         tcon->status = TID_IN_TCON;
543         spin_unlock(&tcon->tc_lock);
544
545         tree = kzalloc(MAX_TREE_SIZE, GFP_KERNEL);
546         if (!tree) {
547                 rc = -ENOMEM;
548                 goto out;
549         }
550
551         if (tcon->ipc) {
552                 cifs_server_lock(server);
553                 scnprintf(tree, MAX_TREE_SIZE, "\\\\%s\\IPC$", server->hostname);
554                 cifs_server_unlock(server);
555                 rc = ops->tree_connect(xid, tcon->ses, tree, tcon, nlsc);
556                 goto out;
557         }
558
559         sb = cifs_get_dfs_tcon_super(tcon);
560         if (!IS_ERR(sb))
561                 cifs_sb = CIFS_SB(sb);
562
563         /*
564          * Tree connect to last share in @tcon->tree_name whether dfs super or
565          * cached dfs referral was not found.
566          */
567         if (!cifs_sb || !server->leaf_fullpath ||
568             dfs_cache_noreq_find(server->leaf_fullpath + 1, &ref, &tl)) {
569                 rc = ops->tree_connect(xid, tcon->ses, tcon->tree_name, tcon,
570                                        cifs_sb ? cifs_sb->local_nls : nlsc);
571                 goto out;
572         }
573
574         rc = tree_connect_dfs_target(xid, tcon, cifs_sb, tree, ref.server_type == DFS_TYPE_LINK,
575                                      &tl);
576         free_dfs_info_param(&ref);
577
578 out:
579         kfree(tree);
580         cifs_put_tcp_super(sb);
581
582         if (rc) {
583                 spin_lock(&tcon->tc_lock);
584                 if (tcon->status == TID_IN_TCON)
585                         tcon->status = TID_NEED_TCON;
586                 spin_unlock(&tcon->tc_lock);
587         } else {
588                 spin_lock(&tcon->tc_lock);
589                 if (tcon->status == TID_IN_TCON)
590                         tcon->status = TID_GOOD;
591                 spin_unlock(&tcon->tc_lock);
592                 tcon->need_reconnect = false;
593         }
594
595         return rc;
596 }