]> Git Repo - linux.git/blob - fs/cifs/dfs.c
workqueue: Unbind kworkers before sending them to exit()
[linux.git] / fs / cifs / 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 /*
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         if (!rc) {
99                 struct cifs_ses *ses = mnt_ctx->ses;
100
101                 mutex_lock(&ses->session_mutex);
102                 ses->dfs_root_ses = mnt_ctx->root_ses;
103                 mutex_unlock(&ses->session_mutex);
104         }
105         return rc;
106 }
107
108 static void set_root_ses(struct cifs_mount_ctx *mnt_ctx)
109 {
110         if (mnt_ctx->ses) {
111                 spin_lock(&cifs_tcp_ses_lock);
112                 mnt_ctx->ses->ses_count++;
113                 spin_unlock(&cifs_tcp_ses_lock);
114                 dfs_cache_add_refsrv_session(&mnt_ctx->mount_id, mnt_ctx->ses);
115         }
116         mnt_ctx->root_ses = mnt_ctx->ses;
117 }
118
119 static int get_dfs_conn(struct cifs_mount_ctx *mnt_ctx, const char *ref_path, const char *full_path,
120                         const struct dfs_cache_tgt_iterator *tit)
121 {
122         struct smb3_fs_context *ctx = mnt_ctx->fs_ctx;
123         struct dfs_info3_param ref = {};
124         int rc;
125
126         rc = dfs_cache_get_tgt_referral(ref_path + 1, tit, &ref);
127         if (rc)
128                 return rc;
129
130         rc = dfs_parse_target_referral(full_path + 1, &ref, ctx);
131         if (rc)
132                 goto out;
133
134         cifs_mount_put_conns(mnt_ctx);
135         rc = get_session(mnt_ctx, ref_path);
136         if (rc)
137                 goto out;
138
139         if (ref.flags & DFSREF_REFERRAL_SERVER)
140                 set_root_ses(mnt_ctx);
141
142         rc = -EREMOTE;
143         if (ref.flags & DFSREF_STORAGE_SERVER) {
144                 rc = cifs_mount_get_tcon(mnt_ctx);
145                 if (rc)
146                         goto out;
147
148                 /* some servers may not advertise referral capability under ref.flags */
149                 if (!(ref.flags & DFSREF_REFERRAL_SERVER) &&
150                     is_tcon_dfs(mnt_ctx->tcon))
151                         set_root_ses(mnt_ctx);
152
153                 rc = cifs_is_path_remote(mnt_ctx);
154         }
155
156 out:
157         free_dfs_info_param(&ref);
158         return rc;
159 }
160
161 static int __dfs_mount_share(struct cifs_mount_ctx *mnt_ctx)
162 {
163         struct cifs_sb_info *cifs_sb = mnt_ctx->cifs_sb;
164         struct smb3_fs_context *ctx = mnt_ctx->fs_ctx;
165         char *ref_path = NULL, *full_path = NULL;
166         struct dfs_cache_tgt_iterator *tit;
167         struct TCP_Server_Info *server;
168         char *origin_fullpath = NULL;
169         int num_links = 0;
170         int rc;
171
172         ref_path = dfs_get_path(cifs_sb, ctx->UNC);
173         if (IS_ERR(ref_path))
174                 return PTR_ERR(ref_path);
175
176         full_path = build_unc_path_to_root(ctx, cifs_sb, true);
177         if (IS_ERR(full_path)) {
178                 rc = PTR_ERR(full_path);
179                 full_path = NULL;
180                 goto out;
181         }
182
183         origin_fullpath = kstrdup(full_path, GFP_KERNEL);
184         if (!origin_fullpath) {
185                 rc = -ENOMEM;
186                 goto out;
187         }
188
189         do {
190                 struct dfs_cache_tgt_list tl = DFS_CACHE_TGT_LIST_INIT(tl);
191
192                 rc = dfs_get_referral(mnt_ctx, ref_path + 1, NULL, &tl);
193                 if (rc)
194                         break;
195
196                 tit = dfs_cache_get_tgt_iterator(&tl);
197                 if (!tit) {
198                         cifs_dbg(VFS, "%s: dfs referral (%s) with no targets\n", __func__,
199                                  ref_path + 1);
200                         rc = -ENOENT;
201                         dfs_cache_free_tgts(&tl);
202                         break;
203                 }
204
205                 do {
206                         rc = get_dfs_conn(mnt_ctx, ref_path, full_path, tit);
207                         if (!rc)
208                                 break;
209                         if (rc == -EREMOTE) {
210                                 if (++num_links > MAX_NESTED_LINKS) {
211                                         rc = -ELOOP;
212                                         break;
213                                 }
214                                 kfree(ref_path);
215                                 kfree(full_path);
216                                 ref_path = full_path = NULL;
217
218                                 full_path = build_unc_path_to_root(ctx, cifs_sb, true);
219                                 if (IS_ERR(full_path)) {
220                                         rc = PTR_ERR(full_path);
221                                         full_path = NULL;
222                                 } else {
223                                         ref_path = dfs_get_path(cifs_sb, full_path);
224                                         if (IS_ERR(ref_path)) {
225                                                 rc = PTR_ERR(ref_path);
226                                                 ref_path = NULL;
227                                         }
228                                 }
229                                 break;
230                         }
231                 } while ((tit = dfs_cache_get_next_tgt(&tl, tit)));
232                 dfs_cache_free_tgts(&tl);
233         } while (rc == -EREMOTE);
234
235         if (!rc) {
236                 server = mnt_ctx->server;
237
238                 mutex_lock(&server->refpath_lock);
239                 server->origin_fullpath = origin_fullpath;
240                 server->current_fullpath = server->leaf_fullpath;
241                 mutex_unlock(&server->refpath_lock);
242                 origin_fullpath = NULL;
243         }
244
245 out:
246         kfree(origin_fullpath);
247         kfree(ref_path);
248         kfree(full_path);
249         return rc;
250 }
251
252 int dfs_mount_share(struct cifs_mount_ctx *mnt_ctx, bool *isdfs)
253 {
254         struct cifs_sb_info *cifs_sb = mnt_ctx->cifs_sb;
255         struct smb3_fs_context *ctx = mnt_ctx->fs_ctx;
256         int rc;
257
258         *isdfs = false;
259
260         rc = get_session(mnt_ctx, NULL);
261         if (rc)
262                 return rc;
263         mnt_ctx->root_ses = mnt_ctx->ses;
264         /*
265          * If called with 'nodfs' mount option, then skip DFS resolving.  Otherwise unconditionally
266          * try to get an DFS referral (even cached) to determine whether it is an DFS mount.
267          *
268          * Skip prefix path to provide support for DFS referrals from w2k8 servers which don't seem
269          * to respond with PATH_NOT_COVERED to requests that include the prefix.
270          */
271         if ((cifs_sb->mnt_cifs_flags & CIFS_MOUNT_NO_DFS) ||
272             dfs_get_referral(mnt_ctx, ctx->UNC + 1, NULL, NULL)) {
273                 rc = cifs_mount_get_tcon(mnt_ctx);
274                 if (rc)
275                         return rc;
276
277                 rc = cifs_is_path_remote(mnt_ctx);
278                 if (!rc || rc != -EREMOTE)
279                         return rc;
280         }
281
282         *isdfs = true;
283         set_root_ses(mnt_ctx);
284
285         return __dfs_mount_share(mnt_ctx);
286 }
287
288 /* Update dfs referral path of superblock */
289 static int update_server_fullpath(struct TCP_Server_Info *server, struct cifs_sb_info *cifs_sb,
290                                   const char *target)
291 {
292         int rc = 0;
293         size_t len = strlen(target);
294         char *refpath, *npath;
295
296         if (unlikely(len < 2 || *target != '\\'))
297                 return -EINVAL;
298
299         if (target[1] == '\\') {
300                 len += 1;
301                 refpath = kmalloc(len, GFP_KERNEL);
302                 if (!refpath)
303                         return -ENOMEM;
304
305                 scnprintf(refpath, len, "%s", target);
306         } else {
307                 len += sizeof("\\");
308                 refpath = kmalloc(len, GFP_KERNEL);
309                 if (!refpath)
310                         return -ENOMEM;
311
312                 scnprintf(refpath, len, "\\%s", target);
313         }
314
315         npath = dfs_cache_canonical_path(refpath, cifs_sb->local_nls, cifs_remap(cifs_sb));
316         kfree(refpath);
317
318         if (IS_ERR(npath)) {
319                 rc = PTR_ERR(npath);
320         } else {
321                 mutex_lock(&server->refpath_lock);
322                 kfree(server->leaf_fullpath);
323                 server->leaf_fullpath = npath;
324                 mutex_unlock(&server->refpath_lock);
325                 server->current_fullpath = server->leaf_fullpath;
326         }
327         return rc;
328 }
329
330 static int target_share_matches_server(struct TCP_Server_Info *server, const char *tcp_host,
331                                        size_t tcp_host_len, char *share, bool *target_match)
332 {
333         int rc = 0;
334         const char *dfs_host;
335         size_t dfs_host_len;
336
337         *target_match = true;
338         extract_unc_hostname(share, &dfs_host, &dfs_host_len);
339
340         /* Check if hostnames or addresses match */
341         if (dfs_host_len != tcp_host_len || strncasecmp(dfs_host, tcp_host, dfs_host_len) != 0) {
342                 cifs_dbg(FYI, "%s: %.*s doesn't match %.*s\n", __func__, (int)dfs_host_len,
343                          dfs_host, (int)tcp_host_len, tcp_host);
344                 rc = match_target_ip(server, dfs_host, dfs_host_len, target_match);
345                 if (rc)
346                         cifs_dbg(VFS, "%s: failed to match target ip: %d\n", __func__, rc);
347         }
348         return rc;
349 }
350
351 static int __tree_connect_dfs_target(const unsigned int xid, struct cifs_tcon *tcon,
352                                      struct cifs_sb_info *cifs_sb, char *tree, bool islink,
353                                      struct dfs_cache_tgt_list *tl)
354 {
355         int rc;
356         struct TCP_Server_Info *server = tcon->ses->server;
357         const struct smb_version_operations *ops = server->ops;
358         struct cifs_ses *root_ses = CIFS_DFS_ROOT_SES(tcon->ses);
359         struct cifs_tcon *ipc = root_ses->tcon_ipc;
360         char *share = NULL, *prefix = NULL;
361         const char *tcp_host;
362         size_t tcp_host_len;
363         struct dfs_cache_tgt_iterator *tit;
364         bool target_match;
365
366         extract_unc_hostname(server->hostname, &tcp_host, &tcp_host_len);
367
368         tit = dfs_cache_get_tgt_iterator(tl);
369         if (!tit) {
370                 rc = -ENOENT;
371                 goto out;
372         }
373
374         /* Try to tree connect to all dfs targets */
375         for (; tit; tit = dfs_cache_get_next_tgt(tl, tit)) {
376                 const char *target = dfs_cache_get_tgt_name(tit);
377                 struct dfs_cache_tgt_list ntl = DFS_CACHE_TGT_LIST_INIT(ntl);
378
379                 kfree(share);
380                 kfree(prefix);
381                 share = prefix = NULL;
382
383                 /* Check if share matches with tcp ses */
384                 rc = dfs_cache_get_tgt_share(server->current_fullpath + 1, tit, &share, &prefix);
385                 if (rc) {
386                         cifs_dbg(VFS, "%s: failed to parse target share: %d\n", __func__, rc);
387                         break;
388                 }
389
390                 rc = target_share_matches_server(server, tcp_host, tcp_host_len, share,
391                                                  &target_match);
392                 if (rc)
393                         break;
394                 if (!target_match) {
395                         rc = -EHOSTUNREACH;
396                         continue;
397                 }
398
399                 dfs_cache_noreq_update_tgthint(server->current_fullpath + 1, tit);
400
401                 if (ipc->need_reconnect) {
402                         scnprintf(tree, MAX_TREE_SIZE, "\\\\%s\\IPC$", server->hostname);
403                         rc = ops->tree_connect(xid, ipc->ses, tree, ipc, cifs_sb->local_nls);
404                         if (rc)
405                                 break;
406                 }
407
408                 scnprintf(tree, MAX_TREE_SIZE, "\\%s", share);
409                 if (!islink) {
410                         rc = ops->tree_connect(xid, tcon->ses, tree, tcon, cifs_sb->local_nls);
411                         break;
412                 }
413                 /*
414                  * If no dfs referrals were returned from link target, then just do a TREE_CONNECT
415                  * to it.  Otherwise, cache the dfs referral and then mark current tcp ses for
416                  * reconnect so either the demultiplex thread or the echo worker will reconnect to
417                  * newly resolved target.
418                  */
419                 if (dfs_cache_find(xid, root_ses, cifs_sb->local_nls, cifs_remap(cifs_sb), target,
420                                    NULL, &ntl)) {
421                         rc = ops->tree_connect(xid, tcon->ses, tree, tcon, cifs_sb->local_nls);
422                         if (rc)
423                                 continue;
424
425                         rc = cifs_update_super_prepath(cifs_sb, prefix);
426                 } else {
427                         /* Target is another dfs share */
428                         rc = update_server_fullpath(server, cifs_sb, target);
429                         dfs_cache_free_tgts(tl);
430
431                         if (!rc) {
432                                 rc = -EREMOTE;
433                                 list_replace_init(&ntl.tl_list, &tl->tl_list);
434                         } else
435                                 dfs_cache_free_tgts(&ntl);
436                 }
437                 break;
438         }
439
440 out:
441         kfree(share);
442         kfree(prefix);
443
444         return rc;
445 }
446
447 static int tree_connect_dfs_target(const unsigned int xid, struct cifs_tcon *tcon,
448                                    struct cifs_sb_info *cifs_sb, char *tree, bool islink,
449                                    struct dfs_cache_tgt_list *tl)
450 {
451         int rc;
452         int num_links = 0;
453         struct TCP_Server_Info *server = tcon->ses->server;
454         char *old_fullpath = server->leaf_fullpath;
455
456         do {
457                 rc = __tree_connect_dfs_target(xid, tcon, cifs_sb, tree, islink, tl);
458                 if (!rc || rc != -EREMOTE)
459                         break;
460         } while (rc = -ELOOP, ++num_links < MAX_NESTED_LINKS);
461         /*
462          * If we couldn't tree connect to any targets from last referral path, then
463          * retry it from newly resolved dfs referral.
464          */
465         if (rc && server->leaf_fullpath != old_fullpath)
466                 cifs_signal_cifsd_for_reconnect(server, true);
467
468         dfs_cache_free_tgts(tl);
469         return rc;
470 }
471
472 int cifs_tree_connect(const unsigned int xid, struct cifs_tcon *tcon, const struct nls_table *nlsc)
473 {
474         int rc;
475         struct TCP_Server_Info *server = tcon->ses->server;
476         const struct smb_version_operations *ops = server->ops;
477         struct super_block *sb = NULL;
478         struct cifs_sb_info *cifs_sb;
479         struct dfs_cache_tgt_list tl = DFS_CACHE_TGT_LIST_INIT(tl);
480         char *tree;
481         struct dfs_info3_param ref = {0};
482
483         /* only send once per connect */
484         spin_lock(&tcon->tc_lock);
485         if (tcon->ses->ses_status != SES_GOOD ||
486             (tcon->status != TID_NEW &&
487             tcon->status != TID_NEED_TCON)) {
488                 spin_unlock(&tcon->tc_lock);
489                 return 0;
490         }
491         tcon->status = TID_IN_TCON;
492         spin_unlock(&tcon->tc_lock);
493
494         tree = kzalloc(MAX_TREE_SIZE, GFP_KERNEL);
495         if (!tree) {
496                 rc = -ENOMEM;
497                 goto out;
498         }
499
500         if (tcon->ipc) {
501                 scnprintf(tree, MAX_TREE_SIZE, "\\\\%s\\IPC$", server->hostname);
502                 rc = ops->tree_connect(xid, tcon->ses, tree, tcon, nlsc);
503                 goto out;
504         }
505
506         sb = cifs_get_tcp_super(server);
507         if (IS_ERR(sb)) {
508                 rc = PTR_ERR(sb);
509                 cifs_dbg(VFS, "%s: could not find superblock: %d\n", __func__, rc);
510                 goto out;
511         }
512
513         cifs_sb = CIFS_SB(sb);
514
515         /* If it is not dfs or there was no cached dfs referral, then reconnect to same share */
516         if (!server->current_fullpath ||
517             dfs_cache_noreq_find(server->current_fullpath + 1, &ref, &tl)) {
518                 rc = ops->tree_connect(xid, tcon->ses, tcon->tree_name, tcon, cifs_sb->local_nls);
519                 goto out;
520         }
521
522         rc = tree_connect_dfs_target(xid, tcon, cifs_sb, tree, ref.server_type == DFS_TYPE_LINK,
523                                      &tl);
524         free_dfs_info_param(&ref);
525
526 out:
527         kfree(tree);
528         cifs_put_tcp_super(sb);
529
530         if (rc) {
531                 spin_lock(&tcon->tc_lock);
532                 if (tcon->status == TID_IN_TCON)
533                         tcon->status = TID_NEED_TCON;
534                 spin_unlock(&tcon->tc_lock);
535         } else {
536                 spin_lock(&tcon->tc_lock);
537                 if (tcon->status == TID_IN_TCON)
538                         tcon->status = TID_GOOD;
539                 spin_unlock(&tcon->tc_lock);
540                 tcon->need_reconnect = false;
541         }
542
543         return rc;
544 }
This page took 0.063969 seconds and 4 git commands to generate.