| // SPDX-License-Identifier: GPL-2.0-or-later |
| /* |
| * Copyright (C) 2018 Samsung Electronics Co., Ltd. |
| */ |
| |
| #include <linux/list.h> |
| #include <linux/jhash.h> |
| #include <linux/slab.h> |
| #include <linux/rwsem.h> |
| #include <linux/parser.h> |
| #include <linux/namei.h> |
| #include <linux/sched.h> |
| #include <linux/mm.h> |
| |
| #include "share_config.h" |
| #include "user_config.h" |
| #include "user_session.h" |
| #include "../connection.h" |
| #include "../transport_ipc.h" |
| #include "../misc.h" |
| |
| #define SHARE_HASH_BITS 3 |
| static DEFINE_HASHTABLE(shares_table, SHARE_HASH_BITS); |
| static DECLARE_RWSEM(shares_table_lock); |
| |
| struct ksmbd_veto_pattern { |
| char *pattern; |
| struct list_head list; |
| }; |
| |
| static unsigned int share_name_hash(const char *name) |
| { |
| return jhash(name, strlen(name), 0); |
| } |
| |
| static void kill_share(struct ksmbd_share_config *share) |
| { |
| while (!list_empty(&share->veto_list)) { |
| struct ksmbd_veto_pattern *p; |
| |
| p = list_entry(share->veto_list.next, |
| struct ksmbd_veto_pattern, |
| list); |
| list_del(&p->list); |
| kfree(p->pattern); |
| kfree(p); |
| } |
| |
| if (share->path) |
| path_put(&share->vfs_path); |
| kfree(share->name); |
| kfree(share->path); |
| kfree(share); |
| } |
| |
| void ksmbd_share_config_del(struct ksmbd_share_config *share) |
| { |
| down_write(&shares_table_lock); |
| hash_del(&share->hlist); |
| up_write(&shares_table_lock); |
| } |
| |
| void __ksmbd_share_config_put(struct ksmbd_share_config *share) |
| { |
| ksmbd_share_config_del(share); |
| kill_share(share); |
| } |
| |
| static struct ksmbd_share_config * |
| __get_share_config(struct ksmbd_share_config *share) |
| { |
| if (!atomic_inc_not_zero(&share->refcount)) |
| return NULL; |
| return share; |
| } |
| |
| static struct ksmbd_share_config *__share_lookup(const char *name) |
| { |
| struct ksmbd_share_config *share; |
| unsigned int key = share_name_hash(name); |
| |
| hash_for_each_possible(shares_table, share, hlist, key) { |
| if (!strcmp(name, share->name)) |
| return share; |
| } |
| return NULL; |
| } |
| |
| static int parse_veto_list(struct ksmbd_share_config *share, |
| char *veto_list, |
| int veto_list_sz) |
| { |
| int sz = 0; |
| |
| if (!veto_list_sz) |
| return 0; |
| |
| while (veto_list_sz > 0) { |
| struct ksmbd_veto_pattern *p; |
| |
| sz = strlen(veto_list); |
| if (!sz) |
| break; |
| |
| p = kzalloc(sizeof(struct ksmbd_veto_pattern), GFP_KERNEL); |
| if (!p) |
| return -ENOMEM; |
| |
| p->pattern = kstrdup(veto_list, GFP_KERNEL); |
| if (!p->pattern) { |
| kfree(p); |
| return -ENOMEM; |
| } |
| |
| list_add(&p->list, &share->veto_list); |
| |
| veto_list += sz + 1; |
| veto_list_sz -= (sz + 1); |
| } |
| |
| return 0; |
| } |
| |
| static struct ksmbd_share_config *share_config_request(struct ksmbd_work *work, |
| const char *name) |
| { |
| struct ksmbd_share_config_response *resp; |
| struct ksmbd_share_config *share = NULL; |
| struct ksmbd_share_config *lookup; |
| struct unicode_map *um = work->conn->um; |
| int ret; |
| |
| resp = ksmbd_ipc_share_config_request(name); |
| if (!resp) |
| return NULL; |
| |
| if (resp->flags == KSMBD_SHARE_FLAG_INVALID) |
| goto out; |
| |
| if (*resp->share_name) { |
| char *cf_resp_name; |
| bool equal; |
| |
| cf_resp_name = ksmbd_casefold_sharename(um, resp->share_name); |
| if (IS_ERR(cf_resp_name)) |
| goto out; |
| equal = !strcmp(cf_resp_name, name); |
| kfree(cf_resp_name); |
| if (!equal) |
| goto out; |
| } |
| |
| share = kzalloc(sizeof(struct ksmbd_share_config), GFP_KERNEL); |
| if (!share) |
| goto out; |
| |
| share->flags = resp->flags; |
| atomic_set(&share->refcount, 1); |
| INIT_LIST_HEAD(&share->veto_list); |
| share->name = kstrdup(name, GFP_KERNEL); |
| |
| if (!test_share_config_flag(share, KSMBD_SHARE_FLAG_PIPE)) { |
| int path_len = PATH_MAX; |
| |
| if (resp->payload_sz) |
| path_len = resp->payload_sz - resp->veto_list_sz; |
| |
| share->path = kstrndup(ksmbd_share_config_path(resp), path_len, |
| GFP_KERNEL); |
| if (share->path) { |
| share->path_sz = strlen(share->path); |
| while (share->path_sz > 1 && |
| share->path[share->path_sz - 1] == '/') |
| share->path[--share->path_sz] = '\0'; |
| } |
| share->create_mask = resp->create_mask; |
| share->directory_mask = resp->directory_mask; |
| share->force_create_mode = resp->force_create_mode; |
| share->force_directory_mode = resp->force_directory_mode; |
| share->force_uid = resp->force_uid; |
| share->force_gid = resp->force_gid; |
| ret = parse_veto_list(share, |
| KSMBD_SHARE_CONFIG_VETO_LIST(resp), |
| resp->veto_list_sz); |
| if (!ret && share->path) { |
| if (__ksmbd_override_fsids(work, share)) { |
| kill_share(share); |
| share = NULL; |
| goto out; |
| } |
| |
| ret = kern_path(share->path, 0, &share->vfs_path); |
| ksmbd_revert_fsids(work); |
| if (ret) { |
| ksmbd_debug(SMB, "failed to access '%s'\n", |
| share->path); |
| /* Avoid put_path() */ |
| kfree(share->path); |
| share->path = NULL; |
| } |
| } |
| if (ret || !share->name) { |
| kill_share(share); |
| share = NULL; |
| goto out; |
| } |
| } |
| |
| down_write(&shares_table_lock); |
| lookup = __share_lookup(name); |
| if (lookup) |
| lookup = __get_share_config(lookup); |
| if (!lookup) { |
| hash_add(shares_table, &share->hlist, share_name_hash(name)); |
| } else { |
| kill_share(share); |
| share = lookup; |
| } |
| up_write(&shares_table_lock); |
| |
| out: |
| kvfree(resp); |
| return share; |
| } |
| |
| struct ksmbd_share_config *ksmbd_share_config_get(struct ksmbd_work *work, |
| const char *name) |
| { |
| struct ksmbd_share_config *share; |
| |
| down_read(&shares_table_lock); |
| share = __share_lookup(name); |
| if (share) |
| share = __get_share_config(share); |
| up_read(&shares_table_lock); |
| |
| if (share) |
| return share; |
| return share_config_request(work, name); |
| } |
| |
| bool ksmbd_share_veto_filename(struct ksmbd_share_config *share, |
| const char *filename) |
| { |
| struct ksmbd_veto_pattern *p; |
| |
| list_for_each_entry(p, &share->veto_list, list) { |
| if (match_wildcard(p->pattern, filename)) |
| return true; |
| } |
| return false; |
| } |