blob: d8d03070ae44b46dab1505227f617218d09562e2 [file] [log] [blame]
// 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;
}