]> Git Repo - linux.git/blob - fs/smb/client/dfs.c
mmu_notifiers: call invalidate_range() when invalidating TLBs
[linux.git] / fs / smb / client / dfs.c
1 // SPDX-License-Identifier: GPL-2.0
2 /*
3  * Copyright (c) 2022 Paulo Alcantara <[email protected]>
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                         break;
182
183                 tit = dfs_cache_get_tgt_iterator(&tl);
184                 if (!tit) {
185                         cifs_dbg(VFS, "%s: dfs referral (%s) with no targets\n", __func__,
186                                  ref_path + 1);
187                         rc = -ENOENT;
188                         dfs_cache_free_tgts(&tl);
189                         break;
190                 }
191
192                 do {
193                         rc = get_dfs_conn(mnt_ctx, ref_path, full_path, tit);
194                         if (!rc)
195                                 break;
196                         if (rc == -EREMOTE) {
197                                 if (++num_links > MAX_NESTED_LINKS) {
198                                         rc = -ELOOP;
199                                         break;
200                                 }
201                                 kfree(ref_path);
202                                 kfree(full_path);
203                                 ref_path = full_path = NULL;
204
205                                 full_path = smb3_fs_context_fullpath(ctx, sep);
206                                 if (IS_ERR(full_path)) {
207                                         rc = PTR_ERR(full_path);
208                                         full_path = NULL;
209                                 } else {
210                                         ref_path = dfs_get_path(cifs_sb, full_path);
211                                         if (IS_ERR(ref_path)) {
212                                                 rc = PTR_ERR(ref_path);
213                                                 ref_path = NULL;
214                                         }
215                                 }
216                                 break;
217                         }
218                 } while ((tit = dfs_cache_get_next_tgt(&tl, tit)));
219                 dfs_cache_free_tgts(&tl);
220         } while (rc == -EREMOTE);
221
222         if (!rc) {
223                 tcon = mnt_ctx->tcon;
224
225                 spin_lock(&tcon->tc_lock);
226                 if (!tcon->origin_fullpath) {
227                         tcon->origin_fullpath = origin_fullpath;
228                         origin_fullpath = NULL;
229                 }
230                 spin_unlock(&tcon->tc_lock);
231
232                 if (list_empty(&tcon->dfs_ses_list)) {
233                         list_replace_init(&mnt_ctx->dfs_ses_list,
234                                           &tcon->dfs_ses_list);
235                         queue_delayed_work(dfscache_wq, &tcon->dfs_cache_work,
236                                            dfs_cache_get_ttl() * HZ);
237                 } else {
238                         dfs_put_root_smb_sessions(&mnt_ctx->dfs_ses_list);
239                 }
240         }
241
242 out:
243         kfree(origin_fullpath);
244         kfree(ref_path);
245         kfree(full_path);
246         return rc;
247 }
248
249 int dfs_mount_share(struct cifs_mount_ctx *mnt_ctx, bool *isdfs)
250 {
251         struct smb3_fs_context *ctx = mnt_ctx->fs_ctx;
252         bool nodfs = ctx->nodfs;
253         int rc;
254
255         *isdfs = false;
256         rc = get_session(mnt_ctx, NULL);
257         if (rc)
258                 return rc;
259
260         ctx->dfs_root_ses = mnt_ctx->ses;
261         /*
262          * If called with 'nodfs' mount option, then skip DFS resolving.  Otherwise unconditionally
263          * try to get an DFS referral (even cached) to determine whether it is an DFS mount.
264          *
265          * Skip prefix path to provide support for DFS referrals from w2k8 servers which don't seem
266          * to respond with PATH_NOT_COVERED to requests that include the prefix.
267          */
268         if (!nodfs) {
269                 rc = dfs_get_referral(mnt_ctx, ctx->UNC + 1, NULL, NULL);
270                 if (rc) {
271                         cifs_dbg(FYI, "%s: no dfs referral for %s: %d\n",
272                                  __func__, ctx->UNC + 1, rc);
273                         cifs_dbg(FYI, "%s: assuming non-dfs mount...\n", __func__);
274                         nodfs = true;
275                 }
276         }
277         if (nodfs) {
278                 rc = cifs_mount_get_tcon(mnt_ctx);
279                 if (!rc)
280                         rc = cifs_is_path_remote(mnt_ctx);
281                 return rc;
282         }
283
284         *isdfs = true;
285         add_root_smb_session(mnt_ctx);
286         return __dfs_mount_share(mnt_ctx);
287 }
288
289 /* Update dfs referral path of superblock */
290 static int update_server_fullpath(struct TCP_Server_Info *server, struct cifs_sb_info *cifs_sb,
291                                   const char *target)
292 {
293         int rc = 0;
294         size_t len = strlen(target);
295         char *refpath, *npath;
296
297         if (unlikely(len < 2 || *target != '\\'))
298                 return -EINVAL;
299
300         if (target[1] == '\\') {
301                 len += 1;
302                 refpath = kmalloc(len, GFP_KERNEL);
303                 if (!refpath)
304                         return -ENOMEM;
305
306                 scnprintf(refpath, len, "%s", target);
307         } else {
308                 len += sizeof("\\");
309                 refpath = kmalloc(len, GFP_KERNEL);
310                 if (!refpath)
311                         return -ENOMEM;
312
313                 scnprintf(refpath, len, "\\%s", target);
314         }
315
316         npath = dfs_cache_canonical_path(refpath, cifs_sb->local_nls, cifs_remap(cifs_sb));
317         kfree(refpath);
318
319         if (IS_ERR(npath)) {
320                 rc = PTR_ERR(npath);
321         } else {
322                 mutex_lock(&server->refpath_lock);
323                 spin_lock(&server->srv_lock);
324                 kfree(server->leaf_fullpath);
325                 server->leaf_fullpath = npath;
326                 spin_unlock(&server->srv_lock);
327                 mutex_unlock(&server->refpath_lock);
328         }
329         return rc;
330 }
331
332 static int target_share_matches_server(struct TCP_Server_Info *server, char *share,
333                                        bool *target_match)
334 {
335         int rc = 0;
336         const char *dfs_host;
337         size_t dfs_host_len;
338
339         *target_match = true;
340         extract_unc_hostname(share, &dfs_host, &dfs_host_len);
341
342         /* Check if hostnames or addresses match */
343         cifs_server_lock(server);
344         if (dfs_host_len != strlen(server->hostname) ||
345             strncasecmp(dfs_host, server->hostname, dfs_host_len)) {
346                 cifs_dbg(FYI, "%s: %.*s doesn't match %s\n", __func__,
347                          (int)dfs_host_len, dfs_host, server->hostname);
348                 rc = match_target_ip(server, dfs_host, dfs_host_len, target_match);
349                 if (rc)
350                         cifs_dbg(VFS, "%s: failed to match target ip: %d\n", __func__, rc);
351         }
352         cifs_server_unlock(server);
353         return rc;
354 }
355
356 static void __tree_connect_ipc(const unsigned int xid, char *tree,
357                                struct cifs_sb_info *cifs_sb,
358                                struct cifs_ses *ses)
359 {
360         struct TCP_Server_Info *server = ses->server;
361         struct cifs_tcon *tcon = ses->tcon_ipc;
362         int rc;
363
364         spin_lock(&ses->ses_lock);
365         spin_lock(&ses->chan_lock);
366         if (cifs_chan_needs_reconnect(ses, server) ||
367             ses->ses_status != SES_GOOD) {
368                 spin_unlock(&ses->chan_lock);
369                 spin_unlock(&ses->ses_lock);
370                 cifs_server_dbg(FYI, "%s: skipping ipc reconnect due to disconnected ses\n",
371                                 __func__);
372                 return;
373         }
374         spin_unlock(&ses->chan_lock);
375         spin_unlock(&ses->ses_lock);
376
377         cifs_server_lock(server);
378         scnprintf(tree, MAX_TREE_SIZE, "\\\\%s\\IPC$", server->hostname);
379         cifs_server_unlock(server);
380
381         rc = server->ops->tree_connect(xid, ses, tree, tcon,
382                                        cifs_sb->local_nls);
383         cifs_server_dbg(FYI, "%s: tree_reconnect %s: %d\n", __func__, tree, rc);
384         spin_lock(&tcon->tc_lock);
385         if (rc) {
386                 tcon->status = TID_NEED_TCON;
387         } else {
388                 tcon->status = TID_GOOD;
389                 tcon->need_reconnect = false;
390         }
391         spin_unlock(&tcon->tc_lock);
392 }
393
394 static void tree_connect_ipc(const unsigned int xid, char *tree,
395                              struct cifs_sb_info *cifs_sb,
396                              struct cifs_tcon *tcon)
397 {
398         struct cifs_ses *ses = tcon->ses;
399
400         __tree_connect_ipc(xid, tree, cifs_sb, ses);
401         __tree_connect_ipc(xid, tree, cifs_sb, CIFS_DFS_ROOT_SES(ses));
402 }
403
404 static int __tree_connect_dfs_target(const unsigned int xid, struct cifs_tcon *tcon,
405                                      struct cifs_sb_info *cifs_sb, char *tree, bool islink,
406                                      struct dfs_cache_tgt_list *tl)
407 {
408         int rc;
409         struct TCP_Server_Info *server = tcon->ses->server;
410         const struct smb_version_operations *ops = server->ops;
411         struct cifs_ses *root_ses = CIFS_DFS_ROOT_SES(tcon->ses);
412         char *share = NULL, *prefix = NULL;
413         struct dfs_cache_tgt_iterator *tit;
414         bool target_match;
415
416         tit = dfs_cache_get_tgt_iterator(tl);
417         if (!tit) {
418                 rc = -ENOENT;
419                 goto out;
420         }
421
422         /* Try to tree connect to all dfs targets */
423         for (; tit; tit = dfs_cache_get_next_tgt(tl, tit)) {
424                 const char *target = dfs_cache_get_tgt_name(tit);
425                 struct dfs_cache_tgt_list ntl = DFS_CACHE_TGT_LIST_INIT(ntl);
426
427                 kfree(share);
428                 kfree(prefix);
429                 share = prefix = NULL;
430
431                 /* Check if share matches with tcp ses */
432                 rc = dfs_cache_get_tgt_share(server->leaf_fullpath + 1, tit, &share, &prefix);
433                 if (rc) {
434                         cifs_dbg(VFS, "%s: failed to parse target share: %d\n", __func__, rc);
435                         break;
436                 }
437
438                 rc = target_share_matches_server(server, share, &target_match);
439                 if (rc)
440                         break;
441                 if (!target_match) {
442                         rc = -EHOSTUNREACH;
443                         continue;
444                 }
445
446                 dfs_cache_noreq_update_tgthint(server->leaf_fullpath + 1, tit);
447                 tree_connect_ipc(xid, tree, cifs_sb, tcon);
448
449                 scnprintf(tree, MAX_TREE_SIZE, "\\%s", share);
450                 if (!islink) {
451                         rc = ops->tree_connect(xid, tcon->ses, tree, tcon, cifs_sb->local_nls);
452                         break;
453                 }
454
455                 /*
456                  * If no dfs referrals were returned from link target, then just do a TREE_CONNECT
457                  * to it.  Otherwise, cache the dfs referral and then mark current tcp ses for
458                  * reconnect so either the demultiplex thread or the echo worker will reconnect to
459                  * newly resolved target.
460                  */
461                 if (dfs_cache_find(xid, root_ses, cifs_sb->local_nls, cifs_remap(cifs_sb), target,
462                                    NULL, &ntl)) {
463                         rc = ops->tree_connect(xid, tcon->ses, tree, tcon, cifs_sb->local_nls);
464                         if (rc)
465                                 continue;
466
467                         rc = cifs_update_super_prepath(cifs_sb, prefix);
468                 } else {
469                         /* Target is another dfs share */
470                         rc = update_server_fullpath(server, cifs_sb, target);
471                         dfs_cache_free_tgts(tl);
472
473                         if (!rc) {
474                                 rc = -EREMOTE;
475                                 list_replace_init(&ntl.tl_list, &tl->tl_list);
476                         } else
477                                 dfs_cache_free_tgts(&ntl);
478                 }
479                 break;
480         }
481
482 out:
483         kfree(share);
484         kfree(prefix);
485
486         return rc;
487 }
488
489 static int tree_connect_dfs_target(const unsigned int xid, struct cifs_tcon *tcon,
490                                    struct cifs_sb_info *cifs_sb, char *tree, bool islink,
491                                    struct dfs_cache_tgt_list *tl)
492 {
493         int rc;
494         int num_links = 0;
495         struct TCP_Server_Info *server = tcon->ses->server;
496         char *old_fullpath = server->leaf_fullpath;
497
498         do {
499                 rc = __tree_connect_dfs_target(xid, tcon, cifs_sb, tree, islink, tl);
500                 if (!rc || rc != -EREMOTE)
501                         break;
502         } while (rc = -ELOOP, ++num_links < MAX_NESTED_LINKS);
503         /*
504          * If we couldn't tree connect to any targets from last referral path, then
505          * retry it from newly resolved dfs referral.
506          */
507         if (rc && server->leaf_fullpath != old_fullpath)
508                 cifs_signal_cifsd_for_reconnect(server, true);
509
510         dfs_cache_free_tgts(tl);
511         return rc;
512 }
513
514 int cifs_tree_connect(const unsigned int xid, struct cifs_tcon *tcon, const struct nls_table *nlsc)
515 {
516         int rc;
517         struct TCP_Server_Info *server = tcon->ses->server;
518         const struct smb_version_operations *ops = server->ops;
519         struct dfs_cache_tgt_list tl = DFS_CACHE_TGT_LIST_INIT(tl);
520         struct cifs_sb_info *cifs_sb = NULL;
521         struct super_block *sb = NULL;
522         struct dfs_info3_param ref = {0};
523         char *tree;
524
525         /* only send once per connect */
526         spin_lock(&tcon->tc_lock);
527         if (tcon->status == TID_GOOD) {
528                 spin_unlock(&tcon->tc_lock);
529                 return 0;
530         }
531
532         if (tcon->status != TID_NEW &&
533             tcon->status != TID_NEED_TCON) {
534                 spin_unlock(&tcon->tc_lock);
535                 return -EHOSTDOWN;
536         }
537
538         tcon->status = TID_IN_TCON;
539         spin_unlock(&tcon->tc_lock);
540
541         tree = kzalloc(MAX_TREE_SIZE, GFP_KERNEL);
542         if (!tree) {
543                 rc = -ENOMEM;
544                 goto out;
545         }
546
547         if (tcon->ipc) {
548                 cifs_server_lock(server);
549                 scnprintf(tree, MAX_TREE_SIZE, "\\\\%s\\IPC$", server->hostname);
550                 cifs_server_unlock(server);
551                 rc = ops->tree_connect(xid, tcon->ses, tree, tcon, nlsc);
552                 goto out;
553         }
554
555         sb = cifs_get_dfs_tcon_super(tcon);
556         if (!IS_ERR(sb))
557                 cifs_sb = CIFS_SB(sb);
558
559         /*
560          * Tree connect to last share in @tcon->tree_name whether dfs super or
561          * cached dfs referral was not found.
562          */
563         if (!cifs_sb || !server->leaf_fullpath ||
564             dfs_cache_noreq_find(server->leaf_fullpath + 1, &ref, &tl)) {
565                 rc = ops->tree_connect(xid, tcon->ses, tcon->tree_name, tcon,
566                                        cifs_sb ? cifs_sb->local_nls : nlsc);
567                 goto out;
568         }
569
570         rc = tree_connect_dfs_target(xid, tcon, cifs_sb, tree, ref.server_type == DFS_TYPE_LINK,
571                                      &tl);
572         free_dfs_info_param(&ref);
573
574 out:
575         kfree(tree);
576         cifs_put_tcp_super(sb);
577
578         if (rc) {
579                 spin_lock(&tcon->tc_lock);
580                 if (tcon->status == TID_IN_TCON)
581                         tcon->status = TID_NEED_TCON;
582                 spin_unlock(&tcon->tc_lock);
583         } else {
584                 spin_lock(&tcon->tc_lock);
585                 if (tcon->status == TID_IN_TCON)
586                         tcon->status = TID_GOOD;
587                 spin_unlock(&tcon->tc_lock);
588                 tcon->need_reconnect = false;
589         }
590
591         return rc;
592 }
This page took 0.069812 seconds and 4 git commands to generate.