// SPDX-License-Identifier: GPL-2.0-or-later
/*
* Copyright (C) 2016 Namjae Jeon <linkinjeon@kernel.org>
* Copyright (C) 2018 Samsung Electronics Co., Ltd.
*/
#include <linux/inetdevice.h>
#include <net/addrconf.h>
#include <linux/syscalls.h>
#include <linux/namei.h>
#include <linux/statfs.h>
#include <linux/ethtool.h>
#include <linux/falloc.h>
#include <linux/mount.h>
#include <linux/filelock.h>
#include "glob.h"
#include "smbfsctl.h"
#include "oplock.h"
#include "smbacl.h"
#include "auth.h"
#include "asn1.h"
#include "connection.h"
#include "transport_ipc.h"
#include "transport_rdma.h"
#include "vfs.h"
#include "vfs_cache.h"
#include "misc.h"
#include "server.h"
#include "smb_common.h"
#include "../common/smb2status.h"
#include "ksmbd_work.h"
#include "mgmt/user_config.h"
#include "mgmt/share_config.h"
#include "mgmt/tree_connect.h"
#include "mgmt/user_session.h"
#include "mgmt/ksmbd_ida.h"
#include "ndr.h"
#include "transport_tcp.h"
static void __wbuf(struct ksmbd_work *work, void **req, void **rsp)
{
if (work->next_smb2_rcv_hdr_off) {
*req = ksmbd_req_buf_next(work);
*rsp = ksmbd_resp_buf_next(work);
} else {
*req = smb2_get_msg(work->request_buf);
*rsp = smb2_get_msg(work->response_buf);
}
}
#define WORK_BUFFERS(w, rq, rs) __wbuf((w), (void **)&(rq), (void **)&(rs))
/**
* check_session_id() - check for valid session id in smb header
* @conn: connection instance
* @id: session id from smb header
*
* Return: 1 if valid session id, otherwise 0
*/
static inline bool check_session_id(struct ksmbd_conn *conn, u64 id)
{
struct ksmbd_session *sess;
if (id == 0 || id == -1)
return false ;
sess = ksmbd_session_lookup_all(conn, id);
if (sess) {
ksmbd_user_session_put(sess);
return true ;
}
pr_err("Invalid user session id: %llu\n" , id);
return false ;
}
struct channel *lookup_chann_list(struct ksmbd_session *sess, struct ksmbd_conn *conn)
{
return xa_load(&sess->ksmbd_chann_list, (long )conn);
}
/**
* smb2_get_ksmbd_tcon() - get tree connection information using a tree id.
* @work: smb work
*
* Return: 0 if there is a tree connection matched or these are
* skipable commands, otherwise error
*/
int smb2_get_ksmbd_tcon(struct ksmbd_work *work)
{
struct smb2_hdr *req_hdr = ksmbd_req_buf_next(work);
unsigned int cmd = le16_to_cpu(req_hdr->Command);
unsigned int tree_id;
if (cmd == SMB2_TREE_CONNECT_HE ||
cmd == SMB2_CANCEL_HE ||
cmd == SMB2_LOGOFF_HE) {
ksmbd_debug(SMB, "skip to check tree connect request\n" );
return 0;
}
if (xa_empty(&work->sess->tree_conns)) {
ksmbd_debug(SMB, "NO tree connected\n" );
return -ENOENT;
}
tree_id = le32_to_cpu(req_hdr->Id.SyncId.TreeId);
/*
* If request is not the first in Compound request,
* Just validate tree id in header with work->tcon->id.
*/
if (work->next_smb2_rcv_hdr_off) {
if (!work->tcon) {
pr_err("The first operation in the compound does not have tcon\n" );
return -EINVAL;
}
if (tree_id != UINT_MAX && work->tcon->id != tree_id) {
pr_err("tree id(%u) is different with id(%u) in first operation\n" ,
tree_id, work->tcon->id);
return -EINVAL;
}
return 1;
}
work->tcon = ksmbd_tree_conn_lookup(work->sess, tree_id);
if (!work->tcon) {
pr_err("Invalid tid %d\n" , tree_id);
return -ENOENT;
}
return 1;
}
/**
* smb2_set_err_rsp() - set error response code on smb response
* @work: smb work containing response buffer
*/
void smb2_set_err_rsp(struct ksmbd_work *work)
{
struct smb2_err_rsp *err_rsp;
if (work->next_smb2_rcv_hdr_off)
err_rsp = ksmbd_resp_buf_next(work);
else
err_rsp = smb2_get_msg(work->response_buf);
if (err_rsp->hdr.Status != STATUS_STOPPED_ON_SYMLINK) {
int err;
err_rsp->StructureSize = SMB2_ERROR_STRUCTURE_SIZE2_LE;
err_rsp->ErrorContextCount = 0;
err_rsp->Reserved = 0;
err_rsp->ByteCount = 0;
err_rsp->ErrorData[0] = 0;
err = ksmbd_iov_pin_rsp(work, (void *)err_rsp,
__SMB2_HEADER_STRUCTURE_SIZE +
SMB2_ERROR_STRUCTURE_SIZE2);
if (err)
work->send_no_response = 1;
}
}
/**
* is_smb2_neg_cmd() - is it smb2 negotiation command
* @work: smb work containing smb header
*
* Return: true if smb2 negotiation command, otherwise false
*/
bool is_smb2_neg_cmd(struct ksmbd_work *work)
{
struct smb2_hdr *hdr = smb2_get_msg(work->request_buf);
/* is it SMB2 header ? */
if (hdr->ProtocolId != SMB2_PROTO_NUMBER)
return false ;
/* make sure it is request not response message */
if (hdr->Flags & SMB2_FLAGS_SERVER_TO_REDIR)
return false ;
if (hdr->Command != SMB2_NEGOTIATE)
return false ;
return true ;
}
/**
* is_smb2_rsp() - is it smb2 response
* @work: smb work containing smb response buffer
*
* Return: true if smb2 response, otherwise false
*/
bool is_smb2_rsp(struct ksmbd_work *work)
{
struct smb2_hdr *hdr = smb2_get_msg(work->response_buf);
/* is it SMB2 header ? */
if (hdr->ProtocolId != SMB2_PROTO_NUMBER)
return false ;
/* make sure it is response not request message */
if (!(hdr->Flags & SMB2_FLAGS_SERVER_TO_REDIR))
return false ;
return true ;
}
/**
* get_smb2_cmd_val() - get smb command code from smb header
* @work: smb work containing smb request buffer
*
* Return: smb2 request command value
*/
u16 get_smb2_cmd_val(struct ksmbd_work *work)
{
struct smb2_hdr *rcv_hdr;
if (work->next_smb2_rcv_hdr_off)
rcv_hdr = ksmbd_req_buf_next(work);
else
rcv_hdr = smb2_get_msg(work->request_buf);
return le16_to_cpu(rcv_hdr->Command);
}
/**
* set_smb2_rsp_status() - set error response code on smb2 header
* @work: smb work containing response buffer
* @err: error response code
*/
void set_smb2_rsp_status(struct ksmbd_work *work, __le32 err)
{
struct smb2_hdr *rsp_hdr;
rsp_hdr = smb2_get_msg(work->response_buf);
rsp_hdr->Status = err;
work->iov_idx = 0;
work->iov_cnt = 0;
work->next_smb2_rcv_hdr_off = 0;
smb2_set_err_rsp(work);
}
/**
* init_smb2_neg_rsp() - initialize smb2 response for negotiate command
* @work: smb work containing smb request buffer
*
* smb2 negotiate response is sent in reply of smb1 negotiate command for
* dialect auto-negotiation.
*/
int init_smb2_neg_rsp(struct ksmbd_work *work)
{
struct smb2_hdr *rsp_hdr;
struct smb2_negotiate_rsp *rsp;
struct ksmbd_conn *conn = work->conn;
int err;
rsp_hdr = smb2_get_msg(work->response_buf);
memset(rsp_hdr, 0, sizeof (struct smb2_hdr) + 2);
rsp_hdr->ProtocolId = SMB2_PROTO_NUMBER;
rsp_hdr->StructureSize = SMB2_HEADER_STRUCTURE_SIZE;
rsp_hdr->CreditRequest = cpu_to_le16(2);
rsp_hdr->Command = SMB2_NEGOTIATE;
rsp_hdr->Flags = (SMB2_FLAGS_SERVER_TO_REDIR);
rsp_hdr->NextCommand = 0;
rsp_hdr->MessageId = 0;
rsp_hdr->Id.SyncId.ProcessId = 0;
rsp_hdr->Id.SyncId.TreeId = 0;
rsp_hdr->SessionId = 0;
memset(rsp_hdr->Signature, 0, 16);
rsp = smb2_get_msg(work->response_buf);
WARN_ON(ksmbd_conn_good(conn));
rsp->StructureSize = cpu_to_le16(65);
ksmbd_debug(SMB, "conn->dialect 0x%x\n" , conn->dialect);
rsp->DialectRevision = cpu_to_le16(conn->dialect);
/* Not setting conn guid rsp->ServerGUID, as it
* not used by client for identifying connection
*/
rsp->Capabilities = cpu_to_le32(conn->vals->capabilities);
/* Default Max Message Size till SMB2.0, 64K*/
rsp->MaxTransactSize = cpu_to_le32(conn->vals->max_trans_size);
rsp->MaxReadSize = cpu_to_le32(conn->vals->max_read_size);
rsp->MaxWriteSize = cpu_to_le32(conn->vals->max_write_size);
rsp->SystemTime = cpu_to_le64(ksmbd_systime());
rsp->ServerStartTime = 0;
rsp->SecurityBufferOffset = cpu_to_le16(128);
rsp->SecurityBufferLength = cpu_to_le16(AUTH_GSS_LENGTH);
ksmbd_copy_gss_neg_header((char *)(&rsp->hdr) +
le16_to_cpu(rsp->SecurityBufferOffset));
rsp->SecurityMode = SMB2_NEGOTIATE_SIGNING_ENABLED_LE;
if (server_conf.signing == KSMBD_CONFIG_OPT_MANDATORY)
rsp->SecurityMode |= SMB2_NEGOTIATE_SIGNING_REQUIRED_LE;
err = ksmbd_iov_pin_rsp(work, rsp,
sizeof (struct smb2_negotiate_rsp) + AUTH_GSS_LENGTH);
if (err)
return err;
conn->use_spnego = true ;
ksmbd_conn_set_need_negotiate(conn);
return 0;
}
/**
* smb2_set_rsp_credits() - set number of credits in response buffer
* @work: smb work containing smb response buffer
*/
int smb2_set_rsp_credits(struct ksmbd_work *work)
{
struct smb2_hdr *req_hdr = ksmbd_req_buf_next(work);
struct smb2_hdr *hdr = ksmbd_resp_buf_next(work);
struct ksmbd_conn *conn = work->conn;
unsigned short credits_requested, aux_max;
unsigned short credit_charge, credits_granted = 0;
if (work->send_no_response)
return 0;
hdr->CreditCharge = req_hdr->CreditCharge;
if (conn->total_credits > conn->vals->max_credits) {
hdr->CreditRequest = 0;
pr_err("Total credits overflow: %d\n" , conn->total_credits);
return -EINVAL;
}
credit_charge = max_t(unsigned short ,
le16_to_cpu(req_hdr->CreditCharge), 1);
if (credit_charge > conn->total_credits) {
ksmbd_debug(SMB, "Insufficient credits granted, given: %u, granted: %u\n" ,
credit_charge, conn->total_credits);
return -EINVAL;
}
conn->total_credits -= credit_charge;
conn->outstanding_credits -= credit_charge;
credits_requested = max_t(unsigned short ,
le16_to_cpu(req_hdr->CreditRequest), 1);
/* according to smb2.credits smbtorture, Windows server
* 2016 or later grant up to 8192 credits at once.
*
* TODO: Need to adjuct CreditRequest value according to
* current cpu load
*/
if (hdr->Command == SMB2_NEGOTIATE)
aux_max = 1;
else
aux_max = conn->vals->max_credits - conn->total_credits;
credits_granted = min_t(unsigned short , credits_requested, aux_max);
conn->total_credits += credits_granted;
work->credits_granted += credits_granted;
if (!req_hdr->NextCommand) {
/* Update CreditRequest in last request */
hdr->CreditRequest = cpu_to_le16(work->credits_granted);
}
ksmbd_debug(SMB,
"credits: requested[%d] granted[%d] total_granted[%d]\n" ,
credits_requested, credits_granted,
conn->total_credits);
return 0;
}
/**
* init_chained_smb2_rsp() - initialize smb2 chained response
* @work: smb work containing smb response buffer
*/
static void init_chained_smb2_rsp(struct ksmbd_work *work)
{
struct smb2_hdr *req = ksmbd_req_buf_next(work);
struct smb2_hdr *rsp = ksmbd_resp_buf_next(work);
struct smb2_hdr *rsp_hdr;
struct smb2_hdr *rcv_hdr;
int next_hdr_offset = 0;
int len, new_len;
/* Len of this response = updated RFC len - offset of previous cmd
* in the compound rsp
*/
/* Storing the current local FID which may be needed by subsequent
* command in the compound request
*/
if (req->Command == SMB2_CREATE && rsp->Status == STATUS_SUCCESS) {
work->compound_fid = ((struct smb2_create_rsp *)rsp)->VolatileFileId;
work->compound_pfid = ((struct smb2_create_rsp *)rsp)->PersistentFileId;
work->compound_sid = le64_to_cpu(rsp->SessionId);
}
len = get_rfc1002_len(work->response_buf) - work->next_smb2_rsp_hdr_off;
next_hdr_offset = le32_to_cpu(req->NextCommand);
new_len = ALIGN(len, 8);
work->iov[work->iov_idx].iov_len += (new_len - len);
inc_rfc1001_len(work->response_buf, new_len - len);
rsp->NextCommand = cpu_to_le32(new_len);
work->next_smb2_rcv_hdr_off += next_hdr_offset;
work->curr_smb2_rsp_hdr_off = work->next_smb2_rsp_hdr_off;
work->next_smb2_rsp_hdr_off += new_len;
ksmbd_debug(SMB,
"Compound req new_len = %d rcv off = %d rsp off = %d\n" ,
new_len, work->next_smb2_rcv_hdr_off,
work->next_smb2_rsp_hdr_off);
rsp_hdr = ksmbd_resp_buf_next(work);
rcv_hdr = ksmbd_req_buf_next(work);
if (!(rcv_hdr->Flags & SMB2_FLAGS_RELATED_OPERATIONS)) {
ksmbd_debug(SMB, "related flag should be set\n" );
work->compound_fid = KSMBD_NO_FID;
work->compound_pfid = KSMBD_NO_FID;
}
memset((char *)rsp_hdr, 0, sizeof (struct smb2_hdr) + 2);
rsp_hdr->ProtocolId = SMB2_PROTO_NUMBER;
rsp_hdr->StructureSize = SMB2_HEADER_STRUCTURE_SIZE;
rsp_hdr->Command = rcv_hdr->Command;
/*
* Message is response. We don't grant oplock yet.
*/
rsp_hdr->Flags = (SMB2_FLAGS_SERVER_TO_REDIR |
SMB2_FLAGS_RELATED_OPERATIONS);
rsp_hdr->NextCommand = 0;
rsp_hdr->MessageId = rcv_hdr->MessageId;
rsp_hdr->Id.SyncId.ProcessId = rcv_hdr->Id.SyncId.ProcessId;
rsp_hdr->Id.SyncId.TreeId = rcv_hdr->Id.SyncId.TreeId;
rsp_hdr->SessionId = rcv_hdr->SessionId;
memcpy(rsp_hdr->Signature, rcv_hdr->Signature, 16);
}
/**
* is_chained_smb2_message() - check for chained command
* @work: smb work containing smb request buffer
*
* Return: true if chained request, otherwise false
*/
bool is_chained_smb2_message(struct ksmbd_work *work)
{
struct smb2_hdr *hdr = smb2_get_msg(work->request_buf);
unsigned int len, next_cmd;
if (hdr->ProtocolId != SMB2_PROTO_NUMBER)
return false ;
hdr = ksmbd_req_buf_next(work);
next_cmd = le32_to_cpu(hdr->NextCommand);
if (next_cmd > 0) {
if ((u64)work->next_smb2_rcv_hdr_off + next_cmd +
__SMB2_HEADER_STRUCTURE_SIZE >
get_rfc1002_len(work->request_buf)) {
pr_err("next command(%u) offset exceeds smb msg size\n" ,
next_cmd);
return false ;
}
if ((u64)get_rfc1002_len(work->response_buf) + MAX_CIFS_SMALL_BUFFER_SIZE >
work->response_sz) {
pr_err("next response offset exceeds response buffer size\n" );
return false ;
}
ksmbd_debug(SMB, "got SMB2 chained command\n" );
init_chained_smb2_rsp(work);
return true ;
} else if (work->next_smb2_rcv_hdr_off) {
/*
* This is last request in chained command,
* align response to 8 byte
*/
len = ALIGN(get_rfc1002_len(work->response_buf), 8);
len = len - get_rfc1002_len(work->response_buf);
if (len) {
ksmbd_debug(SMB, "padding len %u\n" , len);
work->iov[work->iov_idx].iov_len += len;
inc_rfc1001_len(work->response_buf, len);
}
work->curr_smb2_rsp_hdr_off = work->next_smb2_rsp_hdr_off;
}
return false ;
}
/**
* init_smb2_rsp_hdr() - initialize smb2 response
* @work: smb work containing smb request buffer
*
* Return: 0
*/
int init_smb2_rsp_hdr(struct ksmbd_work *work)
{
struct smb2_hdr *rsp_hdr = smb2_get_msg(work->response_buf);
struct smb2_hdr *rcv_hdr = smb2_get_msg(work->request_buf);
memset(rsp_hdr, 0, sizeof (struct smb2_hdr) + 2);
rsp_hdr->ProtocolId = rcv_hdr->ProtocolId;
rsp_hdr->StructureSize = SMB2_HEADER_STRUCTURE_SIZE;
rsp_hdr->Command = rcv_hdr->Command;
/*
* Message is response. We don't grant oplock yet.
*/
rsp_hdr->Flags = (SMB2_FLAGS_SERVER_TO_REDIR);
rsp_hdr->NextCommand = 0;
rsp_hdr->MessageId = rcv_hdr->MessageId;
rsp_hdr->Id.SyncId.ProcessId = rcv_hdr->Id.SyncId.ProcessId;
rsp_hdr->Id.SyncId.TreeId = rcv_hdr->Id.SyncId.TreeId;
rsp_hdr->SessionId = rcv_hdr->SessionId;
memcpy(rsp_hdr->Signature, rcv_hdr->Signature, 16);
return 0;
}
/**
* smb2_allocate_rsp_buf() - allocate smb2 response buffer
* @work: smb work containing smb request buffer
*
* Return: 0 on success, otherwise error
*/
int smb2_allocate_rsp_buf(struct ksmbd_work *work)
{
struct smb2_hdr *hdr = smb2_get_msg(work->request_buf);
size_t small_sz = MAX_CIFS_SMALL_BUFFER_SIZE;
size_t large_sz = small_sz + work->conn->vals->max_trans_size;
size_t sz = small_sz;
int cmd = le16_to_cpu(hdr->Command);
if (cmd == SMB2_IOCTL_HE || cmd == SMB2_QUERY_DIRECTORY_HE)
sz = large_sz;
if (cmd == SMB2_QUERY_INFO_HE) {
struct smb2_query_info_req *req;
if (get_rfc1002_len(work->request_buf) <
offsetof(struct smb2_query_info_req, OutputBufferLength))
return -EINVAL;
req = smb2_get_msg(work->request_buf);
if ((req->InfoType == SMB2_O_INFO_FILE &&
(req->FileInfoClass == FILE_FULL_EA_INFORMATION ||
req->FileInfoClass == FILE_ALL_INFORMATION)) ||
req->InfoType == SMB2_O_INFO_SECURITY)
sz = large_sz;
}
/* allocate large response buf for chained commands */
if (le32_to_cpu(hdr->NextCommand) > 0)
sz = large_sz;
work->response_buf = kvzalloc(sz, KSMBD_DEFAULT_GFP);
if (!work->response_buf)
return -ENOMEM;
work->response_sz = sz;
return 0;
}
/**
* smb2_check_user_session() - check for valid session for a user
* @work: smb work containing smb request buffer
*
* Return: 0 on success, otherwise error
*/
int smb2_check_user_session(struct ksmbd_work *work)
{
struct smb2_hdr *req_hdr = ksmbd_req_buf_next(work);
struct ksmbd_conn *conn = work->conn;
unsigned int cmd = le16_to_cpu(req_hdr->Command);
unsigned long long sess_id;
/*
* SMB2_ECHO, SMB2_NEGOTIATE, SMB2_SESSION_SETUP command do not
* require a session id, so no need to validate user session's for
* these commands.
*/
if (cmd == SMB2_ECHO_HE || cmd == SMB2_NEGOTIATE_HE ||
cmd == SMB2_SESSION_SETUP_HE)
return 0;
if (!ksmbd_conn_good(conn))
return -EIO;
sess_id = le64_to_cpu(req_hdr->SessionId);
/*
* If request is not the first in Compound request,
* Just validate session id in header with work->sess->id.
*/
if (work->next_smb2_rcv_hdr_off) {
if (!work->sess) {
pr_err("The first operation in the compound does not have sess\n" );
return -EINVAL;
}
if (sess_id != ULLONG_MAX && work->sess->id != sess_id) {
pr_err("session id(%llu) is different with the first operation(%lld)\n" ,
sess_id, work->sess->id);
return -EINVAL;
}
return 1;
}
/* Check for validity of user session */
work->sess = ksmbd_session_lookup_all(conn, sess_id);
if (work->sess)
return 1;
ksmbd_debug(SMB, "Invalid user session, Uid %llu\n" , sess_id);
return -ENOENT;
}
/**
* smb2_get_name() - get filename string from on the wire smb format
* @src: source buffer
* @maxlen: maxlen of source string
* @local_nls: nls_table pointer
*
* Return: matching converted filename on success, otherwise error ptr
*/
static char *
smb2_get_name(const char *src, const int maxlen, struct nls_table *local_nls)
{
char *name;
name = smb_strndup_from_utf16(src, maxlen, 1, local_nls);
if (IS_ERR(name)) {
pr_err("failed to get name %ld\n" , PTR_ERR(name));
return name;
}
if (*name == '\0' ) {
kfree(name);
return ERR_PTR(-EINVAL);
}
if (*name == '\\' ) {
pr_err("not allow directory name included leading slash\n" );
kfree(name);
return ERR_PTR(-EINVAL);
}
ksmbd_conv_path_to_unix(name);
ksmbd_strip_last_slash(name);
return name;
}
int setup_async_work(struct ksmbd_work *work, void (*fn)(void **), void **arg)
{
struct ksmbd_conn *conn = work->conn;
int id;
id = ksmbd_acquire_async_msg_id(&conn->async_ida);
if (id < 0) {
pr_err("Failed to alloc async message id\n" );
return id;
}
work->asynchronous = true ;
work->async_id = id;
ksmbd_debug(SMB,
"Send interim Response to inform async request id : %d\n" ,
work->async_id);
work->cancel_fn = fn;
work->cancel_argv = arg;
if (list_empty(&work->async_request_entry)) {
spin_lock(&conn->request_lock);
list_add_tail(&work->async_request_entry, &conn->async_requests);
spin_unlock(&conn->request_lock);
}
return 0;
}
void release_async_work(struct ksmbd_work *work)
{
struct ksmbd_conn *conn = work->conn;
spin_lock(&conn->request_lock);
list_del_init(&work->async_request_entry);
spin_unlock(&conn->request_lock);
work->asynchronous = 0;
work->cancel_fn = NULL;
kfree(work->cancel_argv);
work->cancel_argv = NULL;
if (work->async_id) {
ksmbd_release_id(&conn->async_ida, work->async_id);
work->async_id = 0;
}
}
void smb2_send_interim_resp(struct ksmbd_work *work, __le32 status)
{
struct smb2_hdr *rsp_hdr;
struct ksmbd_work *in_work = ksmbd_alloc_work_struct();
if (!in_work)
return ;
if (allocate_interim_rsp_buf(in_work)) {
pr_err("smb_allocate_rsp_buf failed!\n" );
ksmbd_free_work_struct(in_work);
return ;
}
in_work->conn = work->conn;
memcpy(smb2_get_msg(in_work->response_buf), ksmbd_resp_buf_next(work),
__SMB2_HEADER_STRUCTURE_SIZE);
rsp_hdr = smb2_get_msg(in_work->response_buf);
rsp_hdr->Flags |= SMB2_FLAGS_ASYNC_COMMAND;
rsp_hdr->Id.AsyncId = cpu_to_le64(work->async_id);
smb2_set_err_rsp(in_work);
rsp_hdr->Status = status;
ksmbd_conn_write(in_work);
ksmbd_free_work_struct(in_work);
}
static __le32 smb2_get_reparse_tag_special_file(umode_t mode)
{
if (S_ISDIR(mode) || S_ISREG(mode))
return 0;
if (S_ISLNK(mode))
return IO_REPARSE_TAG_LX_SYMLINK_LE;
else if (S_ISFIFO(mode))
return IO_REPARSE_TAG_LX_FIFO_LE;
else if (S_ISSOCK(mode))
return IO_REPARSE_TAG_AF_UNIX_LE;
else if (S_ISCHR(mode))
return IO_REPARSE_TAG_LX_CHR_LE;
else if (S_ISBLK(mode))
return IO_REPARSE_TAG_LX_BLK_LE;
return 0;
}
/**
* smb2_get_dos_mode() - get file mode in dos format from unix mode
* @stat: kstat containing file mode
* @attribute: attribute flags
*
* Return: converted dos mode
*/
static int smb2_get_dos_mode(struct kstat *stat, int attribute)
{
int attr = 0;
if (S_ISDIR(stat->mode)) {
attr = FILE_ATTRIBUTE_DIRECTORY |
(attribute & (FILE_ATTRIBUTE_HIDDEN | FILE_ATTRIBUTE_SYSTEM));
} else {
attr = (attribute & 0x00005137) | FILE_ATTRIBUTE_ARCHIVE;
attr &= ~(FILE_ATTRIBUTE_DIRECTORY);
if (S_ISREG(stat->mode) && (server_conf.share_fake_fscaps &
FILE_SUPPORTS_SPARSE_FILES))
attr |= FILE_ATTRIBUTE_SPARSE_FILE;
if (smb2_get_reparse_tag_special_file(stat->mode))
attr |= FILE_ATTRIBUTE_REPARSE_POINT;
}
return attr;
}
static void build_preauth_ctxt(struct smb2_preauth_neg_context *pneg_ctxt,
__le16 hash_id)
{
pneg_ctxt->ContextType = SMB2_PREAUTH_INTEGRITY_CAPABILITIES;
pneg_ctxt->DataLength = cpu_to_le16(38);
pneg_ctxt->HashAlgorithmCount = cpu_to_le16(1);
pneg_ctxt->Reserved = cpu_to_le32(0);
pneg_ctxt->SaltLength = cpu_to_le16(SMB311_SALT_SIZE);
get_random_bytes(pneg_ctxt->Salt, SMB311_SALT_SIZE);
pneg_ctxt->HashAlgorithms = hash_id;
}
static void build_encrypt_ctxt(struct smb2_encryption_neg_context *pneg_ctxt,
__le16 cipher_type)
{
pneg_ctxt->ContextType = SMB2_ENCRYPTION_CAPABILITIES;
pneg_ctxt->DataLength = cpu_to_le16(4);
pneg_ctxt->Reserved = cpu_to_le32(0);
pneg_ctxt->CipherCount = cpu_to_le16(1);
pneg_ctxt->Ciphers[0] = cipher_type;
}
static void build_sign_cap_ctxt(struct smb2_signing_capabilities *pneg_ctxt,
__le16 sign_algo)
{
pneg_ctxt->ContextType = SMB2_SIGNING_CAPABILITIES;
pneg_ctxt->DataLength =
cpu_to_le16((sizeof (struct smb2_signing_capabilities) + 2)
- sizeof (struct smb2_neg_context));
pneg_ctxt->Reserved = cpu_to_le32(0);
pneg_ctxt->SigningAlgorithmCount = cpu_to_le16(1);
pneg_ctxt->SigningAlgorithms[0] = sign_algo;
}
static void build_posix_ctxt(struct smb2_posix_neg_context *pneg_ctxt)
{
pneg_ctxt->ContextType = SMB2_POSIX_EXTENSIONS_AVAILABLE;
pneg_ctxt->DataLength = cpu_to_le16(POSIX_CTXT_DATA_LEN);
/* SMB2_CREATE_TAG_POSIX is "0x93AD25509CB411E7B42383DE968BCD7C" */
pneg_ctxt->Name[0] = 0x93;
pneg_ctxt->Name[1] = 0xAD;
pneg_ctxt->Name[2] = 0x25;
pneg_ctxt->Name[3] = 0x50;
pneg_ctxt->Name[4] = 0x9C;
pneg_ctxt->Name[5] = 0xB4;
pneg_ctxt->Name[6] = 0x11;
pneg_ctxt->Name[7] = 0xE7;
pneg_ctxt->Name[8] = 0xB4;
pneg_ctxt->Name[9] = 0x23;
pneg_ctxt->Name[10] = 0x83;
pneg_ctxt->Name[11] = 0xDE;
pneg_ctxt->Name[12] = 0x96;
pneg_ctxt->Name[13] = 0x8B;
pneg_ctxt->Name[14] = 0xCD;
pneg_ctxt->Name[15] = 0x7C;
}
static unsigned int assemble_neg_contexts(struct ksmbd_conn *conn,
struct smb2_negotiate_rsp *rsp)
{
char * const pneg_ctxt = (char *)rsp +
le32_to_cpu(rsp->NegotiateContextOffset);
int neg_ctxt_cnt = 1;
int ctxt_size;
ksmbd_debug(SMB,
"assemble SMB2_PREAUTH_INTEGRITY_CAPABILITIES context\n" );
build_preauth_ctxt((struct smb2_preauth_neg_context *)pneg_ctxt,
conn->preauth_info->Preauth_HashId);
ctxt_size = sizeof (struct smb2_preauth_neg_context);
if (conn->cipher_type) {
/* Round to 8 byte boundary */
ctxt_size = round_up(ctxt_size, 8);
ksmbd_debug(SMB,
"assemble SMB2_ENCRYPTION_CAPABILITIES context\n" );
build_encrypt_ctxt((struct smb2_encryption_neg_context *)
(pneg_ctxt + ctxt_size),
conn->cipher_type);
neg_ctxt_cnt++;
ctxt_size += sizeof (struct smb2_encryption_neg_context) + 2;
}
/* compression context not yet supported */
WARN_ON(conn->compress_algorithm != SMB3_COMPRESS_NONE);
if (conn->posix_ext_supported) {
ctxt_size = round_up(ctxt_size, 8);
ksmbd_debug(SMB,
"assemble SMB2_POSIX_EXTENSIONS_AVAILABLE context\n" );
build_posix_ctxt((struct smb2_posix_neg_context *)
(pneg_ctxt + ctxt_size));
neg_ctxt_cnt++;
ctxt_size += sizeof (struct smb2_posix_neg_context);
}
if (conn->signing_negotiated) {
ctxt_size = round_up(ctxt_size, 8);
ksmbd_debug(SMB,
"assemble SMB2_SIGNING_CAPABILITIES context\n" );
build_sign_cap_ctxt((struct smb2_signing_capabilities *)
(pneg_ctxt + ctxt_size),
conn->signing_algorithm);
neg_ctxt_cnt++;
ctxt_size += sizeof (struct smb2_signing_capabilities) + 2;
}
rsp->NegotiateContextCount = cpu_to_le16(neg_ctxt_cnt);
return ctxt_size + AUTH_GSS_PADDING;
}
static __le32 decode_preauth_ctxt(struct ksmbd_conn *conn,
struct smb2_preauth_neg_context *pneg_ctxt,
int ctxt_len)
{
/*
* sizeof(smb2_preauth_neg_context) assumes SMB311_SALT_SIZE Salt,
* which may not be present. Only check for used HashAlgorithms[1].
*/
if (ctxt_len <
sizeof (struct smb2_neg_context) + MIN_PREAUTH_CTXT_DATA_LEN)
return STATUS_INVALID_PARAMETER;
if (pneg_ctxt->HashAlgorithms != SMB2_PREAUTH_INTEGRITY_SHA512)
return STATUS_NO_PREAUTH_INTEGRITY_HASH_OVERLAP;
conn->preauth_info->Preauth_HashId = SMB2_PREAUTH_INTEGRITY_SHA512;
return STATUS_SUCCESS;
}
static void decode_encrypt_ctxt(struct ksmbd_conn *conn,
struct smb2_encryption_neg_context *pneg_ctxt,
int ctxt_len)
{
int cph_cnt;
int i, cphs_size;
if (sizeof (struct smb2_encryption_neg_context) > ctxt_len) {
pr_err("Invalid SMB2_ENCRYPTION_CAPABILITIES context size\n" );
return ;
}
conn->cipher_type = 0;
cph_cnt = le16_to_cpu(pneg_ctxt->CipherCount);
cphs_size = cph_cnt * sizeof (__le16);
if (sizeof (struct smb2_encryption_neg_context) + cphs_size >
ctxt_len) {
pr_err("Invalid cipher count(%d)\n" , cph_cnt);
return ;
}
if (server_conf.flags & KSMBD_GLOBAL_FLAG_SMB2_ENCRYPTION_OFF)
return ;
for (i = 0; i < cph_cnt; i++) {
if (pneg_ctxt->Ciphers[i] == SMB2_ENCRYPTION_AES128_GCM ||
pneg_ctxt->Ciphers[i] == SMB2_ENCRYPTION_AES128_CCM ||
pneg_ctxt->Ciphers[i] == SMB2_ENCRYPTION_AES256_CCM ||
pneg_ctxt->Ciphers[i] == SMB2_ENCRYPTION_AES256_GCM) {
ksmbd_debug(SMB, "Cipher ID = 0x%x\n" ,
pneg_ctxt->Ciphers[i]);
conn->cipher_type = pneg_ctxt->Ciphers[i];
break ;
}
}
}
/**
* smb3_encryption_negotiated() - checks if server and client agreed on enabling encryption
* @conn: smb connection
*
* Return: true if connection should be encrypted, else false
*/
bool smb3_encryption_negotiated(struct ksmbd_conn *conn)
{
if (!conn->ops->generate_encryptionkey)
return false ;
/*
* SMB 3.0 and 3.0.2 dialects use the SMB2_GLOBAL_CAP_ENCRYPTION flag.
* SMB 3.1.1 uses the cipher_type field.
*/
return (conn->vals->capabilities & SMB2_GLOBAL_CAP_ENCRYPTION) ||
conn->cipher_type;
}
static void decode_compress_ctxt(struct ksmbd_conn *conn,
struct smb2_compression_capabilities_context *pneg_ctxt)
{
conn->compress_algorithm = SMB3_COMPRESS_NONE;
}
static void decode_sign_cap_ctxt(struct ksmbd_conn *conn,
struct smb2_signing_capabilities *pneg_ctxt,
int ctxt_len)
{
int sign_algo_cnt;
int i, sign_alos_size;
if (sizeof (struct smb2_signing_capabilities) > ctxt_len) {
pr_err("Invalid SMB2_SIGNING_CAPABILITIES context length\n" );
return ;
}
conn->signing_negotiated = false ;
sign_algo_cnt = le16_to_cpu(pneg_ctxt->SigningAlgorithmCount);
sign_alos_size = sign_algo_cnt * sizeof (__le16);
if (sizeof (struct smb2_signing_capabilities) + sign_alos_size >
ctxt_len) {
pr_err("Invalid signing algorithm count(%d)\n" , sign_algo_cnt);
return ;
}
for (i = 0; i < sign_algo_cnt; i++) {
if (pneg_ctxt->SigningAlgorithms[i] == SIGNING_ALG_HMAC_SHA256_LE ||
pneg_ctxt->SigningAlgorithms[i] == SIGNING_ALG_AES_CMAC_LE) {
ksmbd_debug(SMB, "Signing Algorithm ID = 0x%x\n" ,
pneg_ctxt->SigningAlgorithms[i]);
conn->signing_negotiated = true ;
conn->signing_algorithm =
pneg_ctxt->SigningAlgorithms[i];
break ;
}
}
}
static __le32 deassemble_neg_contexts(struct ksmbd_conn *conn,
struct smb2_negotiate_req *req,
unsigned int len_of_smb)
{
/* +4 is to account for the RFC1001 len field */
struct smb2_neg_context *pctx = (struct smb2_neg_context *)req;
int i = 0, len_of_ctxts;
unsigned int offset = le32_to_cpu(req->NegotiateContextOffset);
unsigned int neg_ctxt_cnt = le16_to_cpu(req->NegotiateContextCount);
__le32 status = STATUS_INVALID_PARAMETER;
ksmbd_debug(SMB, "decoding %d negotiate contexts\n" , neg_ctxt_cnt);
if (len_of_smb <= offset) {
ksmbd_debug(SMB, "Invalid response: negotiate context offset\n" );
return status;
}
len_of_ctxts = len_of_smb - offset;
while (i++ < neg_ctxt_cnt) {
int clen, ctxt_len;
if (len_of_ctxts < (int )sizeof (struct smb2_neg_context))
break ;
pctx = (struct smb2_neg_context *)((char *)pctx + offset);
clen = le16_to_cpu(pctx->DataLength);
ctxt_len = clen + sizeof (struct smb2_neg_context);
if (ctxt_len > len_of_ctxts)
break ;
if (pctx->ContextType == SMB2_PREAUTH_INTEGRITY_CAPABILITIES) {
ksmbd_debug(SMB,
"deassemble SMB2_PREAUTH_INTEGRITY_CAPABILITIES context\n" );
if (conn->preauth_info->Preauth_HashId)
break ;
status = decode_preauth_ctxt(conn,
(struct smb2_preauth_neg_context *)pctx,
ctxt_len);
if (status != STATUS_SUCCESS)
break ;
} else if (pctx->ContextType == SMB2_ENCRYPTION_CAPABILITIES) {
ksmbd_debug(SMB,
"deassemble SMB2_ENCRYPTION_CAPABILITIES context\n" );
if (conn->cipher_type)
break ;
decode_encrypt_ctxt(conn,
(struct smb2_encryption_neg_context *)pctx,
ctxt_len);
} else if (pctx->ContextType == SMB2_COMPRESSION_CAPABILITIES) {
ksmbd_debug(SMB,
"deassemble SMB2_COMPRESSION_CAPABILITIES context\n" );
if (conn->compress_algorithm)
break ;
decode_compress_ctxt(conn,
(struct smb2_compression_capabilities_context *)pctx);
} else if (pctx->ContextType == SMB2_NETNAME_NEGOTIATE_CONTEXT_ID) {
ksmbd_debug(SMB,
"deassemble SMB2_NETNAME_NEGOTIATE_CONTEXT_ID context\n" );
} else if (pctx->ContextType == SMB2_POSIX_EXTENSIONS_AVAILABLE) {
ksmbd_debug(SMB,
"deassemble SMB2_POSIX_EXTENSIONS_AVAILABLE context\n" );
conn->posix_ext_supported = true ;
} else if (pctx->ContextType == SMB2_SIGNING_CAPABILITIES) {
ksmbd_debug(SMB,
"deassemble SMB2_SIGNING_CAPABILITIES context\n" );
decode_sign_cap_ctxt(conn,
(struct smb2_signing_capabilities *)pctx,
ctxt_len);
}
/* offsets must be 8 byte aligned */
offset = (ctxt_len + 7) & ~0x7;
len_of_ctxts -= offset;
}
return status;
}
/**
* smb2_handle_negotiate() - handler for smb2 negotiate command
* @work: smb work containing smb request buffer
*
* Return: 0
*/
int smb2_handle_negotiate(struct ksmbd_work *work)
{
struct ksmbd_conn *conn = work->conn;
struct smb2_negotiate_req *req = smb2_get_msg(work->request_buf);
struct smb2_negotiate_rsp *rsp = smb2_get_msg(work->response_buf);
int rc = 0;
unsigned int smb2_buf_len, smb2_neg_size, neg_ctxt_len = 0;
__le32 status;
ksmbd_debug(SMB, "Received negotiate request\n" );
conn->need_neg = false ;
if (ksmbd_conn_good(conn)) {
pr_err("conn->tcp_status is already in CifsGood State\n" );
work->send_no_response = 1;
return rc;
}
ksmbd_conn_lock(conn);
smb2_buf_len = get_rfc1002_len(work->request_buf);
smb2_neg_size = offsetof(struct smb2_negotiate_req, Dialects);
if (smb2_neg_size > smb2_buf_len) {
rsp->hdr.Status = STATUS_INVALID_PARAMETER;
rc = -EINVAL;
goto err_out;
}
if (req->DialectCount == 0) {
pr_err("malformed packet\n" );
rsp->hdr.Status = STATUS_INVALID_PARAMETER;
rc = -EINVAL;
goto err_out;
}
if (conn->dialect == SMB311_PROT_ID) {
unsigned int nego_ctxt_off = le32_to_cpu(req->NegotiateContextOffset);
if (smb2_buf_len < nego_ctxt_off) {
rsp->hdr.Status = STATUS_INVALID_PARAMETER;
rc = -EINVAL;
goto err_out;
}
if (smb2_neg_size > nego_ctxt_off) {
rsp->hdr.Status = STATUS_INVALID_PARAMETER;
rc = -EINVAL;
goto err_out;
}
if (smb2_neg_size + le16_to_cpu(req->DialectCount) * sizeof (__le16) >
nego_ctxt_off) {
rsp->hdr.Status = STATUS_INVALID_PARAMETER;
rc = -EINVAL;
goto err_out;
}
} else {
if (smb2_neg_size + le16_to_cpu(req->DialectCount) * sizeof (__le16) >
smb2_buf_len) {
rsp->hdr.Status = STATUS_INVALID_PARAMETER;
rc = -EINVAL;
goto err_out;
}
}
conn->cli_cap = le32_to_cpu(req->Capabilities);
switch (conn->dialect) {
case SMB311_PROT_ID:
conn->preauth_info =
kzalloc(sizeof (struct preauth_integrity_info),
KSMBD_DEFAULT_GFP);
if (!conn->preauth_info) {
rc = -ENOMEM;
rsp->hdr.Status = STATUS_INVALID_PARAMETER;
goto err_out;
}
status = deassemble_neg_contexts(conn, req,
get_rfc1002_len(work->request_buf));
if (status != STATUS_SUCCESS) {
pr_err("deassemble_neg_contexts error(0x%x)\n" ,
status);
rsp->hdr.Status = status;
rc = -EINVAL;
kfree(conn->preauth_info);
conn->preauth_info = NULL;
goto err_out;
}
rc = init_smb3_11_server(conn);
if (rc < 0) {
rsp->hdr.Status = STATUS_INVALID_PARAMETER;
kfree(conn->preauth_info);
conn->preauth_info = NULL;
goto err_out;
}
ksmbd_gen_preauth_integrity_hash(conn,
work->request_buf,
conn->preauth_info->Preauth_HashValue);
rsp->NegotiateContextOffset =
cpu_to_le32(OFFSET_OF_NEG_CONTEXT);
neg_ctxt_len = assemble_neg_contexts(conn, rsp);
break ;
case SMB302_PROT_ID:
init_smb3_02_server(conn);
break ;
case SMB30_PROT_ID:
init_smb3_0_server(conn);
break ;
case SMB21_PROT_ID:
init_smb2_1_server(conn);
break ;
case SMB2X_PROT_ID:
case BAD_PROT_ID:
default :
ksmbd_debug(SMB, "Server dialect :0x%x not supported\n" ,
conn->dialect);
rsp->hdr.Status = STATUS_NOT_SUPPORTED;
rc = -EINVAL;
goto err_out;
}
rsp->Capabilities = cpu_to_le32(conn->vals->capabilities);
/* For stats */
conn->connection_type = conn->dialect;
rsp->MaxTransactSize = cpu_to_le32(conn->vals->max_trans_size);
rsp->MaxReadSize = cpu_to_le32(conn->vals->max_read_size);
rsp->MaxWriteSize = cpu_to_le32(conn->vals->max_write_size);
memcpy(conn->ClientGUID, req->ClientGUID,
SMB2_CLIENT_GUID_SIZE);
conn->cli_sec_mode = le16_to_cpu(req->SecurityMode);
rsp->StructureSize = cpu_to_le16(65);
rsp->DialectRevision = cpu_to_le16(conn->dialect);
/* Not setting conn guid rsp->ServerGUID, as it
* not used by client for identifying server
*/
memset(rsp->ServerGUID, 0, SMB2_CLIENT_GUID_SIZE);
rsp->SystemTime = cpu_to_le64(ksmbd_systime());
rsp->ServerStartTime = 0;
ksmbd_debug(SMB, "negotiate context offset %d, count %d\n" ,
le32_to_cpu(rsp->NegotiateContextOffset),
le16_to_cpu(rsp->NegotiateContextCount));
rsp->SecurityBufferOffset = cpu_to_le16(128);
rsp->SecurityBufferLength = cpu_to_le16(AUTH_GSS_LENGTH);
ksmbd_copy_gss_neg_header((char *)(&rsp->hdr) +
le16_to_cpu(rsp->SecurityBufferOffset));
rsp->SecurityMode = SMB2_NEGOTIATE_SIGNING_ENABLED_LE;
conn->use_spnego = true ;
if ((server_conf.signing == KSMBD_CONFIG_OPT_AUTO ||
server_conf.signing == KSMBD_CONFIG_OPT_DISABLED) &&
req->SecurityMode & SMB2_NEGOTIATE_SIGNING_REQUIRED_LE)
conn->sign = true ;
else if (server_conf.signing == KSMBD_CONFIG_OPT_MANDATORY) {
server_conf.enforced_signing = true ;
rsp->SecurityMode |= SMB2_NEGOTIATE_SIGNING_REQUIRED_LE;
conn->sign = true ;
}
conn->srv_sec_mode = le16_to_cpu(rsp->SecurityMode);
ksmbd_conn_set_need_setup(conn);
err_out:
ksmbd_conn_unlock(conn);
if (rc)
rsp->hdr.Status = STATUS_INSUFFICIENT_RESOURCES;
if (!rc)
rc = ksmbd_iov_pin_rsp(work, rsp,
sizeof (struct smb2_negotiate_rsp) +
AUTH_GSS_LENGTH + neg_ctxt_len);
if (rc < 0)
smb2_set_err_rsp(work);
return rc;
}
static int alloc_preauth_hash(struct ksmbd_session *sess,
struct ksmbd_conn *conn)
{
if (sess->Preauth_HashValue)
return 0;
if (!conn->preauth_info)
return -ENOMEM;
sess->Preauth_HashValue = kmemdup(conn->preauth_info->Preauth_HashValue,
PREAUTH_HASHVALUE_SIZE, KSMBD_DEFAULT_GFP);
if (!sess->Preauth_HashValue)
return -ENOMEM;
return 0;
}
static int generate_preauth_hash(struct ksmbd_work *work)
{
struct ksmbd_conn *conn = work->conn;
struct ksmbd_session *sess = work->sess;
u8 *preauth_hash;
if (conn->dialect != SMB311_PROT_ID)
return 0;
if (conn->binding) {
struct preauth_session *preauth_sess;
preauth_sess = ksmbd_preauth_session_lookup(conn, sess->id);
if (!preauth_sess) {
preauth_sess = ksmbd_preauth_session_alloc(conn, sess->id);
if (!preauth_sess)
return -ENOMEM;
}
preauth_hash = preauth_sess->Preauth_HashValue;
} else {
if (!sess->Preauth_HashValue)
if (alloc_preauth_hash(sess, conn))
return -ENOMEM;
preauth_hash = sess->Preauth_HashValue;
}
ksmbd_gen_preauth_integrity_hash(conn, work->request_buf, preauth_hash);
return 0;
}
static int decode_negotiation_token(struct ksmbd_conn *conn,
struct negotiate_message *negblob,
size_t sz)
{
if (!conn->use_spnego)
return -EINVAL;
if (ksmbd_decode_negTokenInit((char *)negblob, sz, conn)) {
if (ksmbd_decode_negTokenTarg((char *)negblob, sz, conn)) {
conn->auth_mechs |= KSMBD_AUTH_NTLMSSP;
conn->preferred_auth_mech = KSMBD_AUTH_NTLMSSP;
conn->use_spnego = false ;
}
}
return 0;
}
static int ntlm_negotiate(struct ksmbd_work *work,
struct negotiate_message *negblob,
size_t negblob_len, struct smb2_sess_setup_rsp *rsp)
{
struct challenge_message *chgblob;
unsigned char *spnego_blob = NULL;
u16 spnego_blob_len;
char *neg_blob;
int sz, rc;
ksmbd_debug(SMB, "negotiate phase\n" );
rc = ksmbd_decode_ntlmssp_neg_blob(negblob, negblob_len, work->conn);
if (rc)
return rc;
sz = le16_to_cpu(rsp->SecurityBufferOffset);
chgblob = (struct challenge_message *)rsp->Buffer;
memset(chgblob, 0, sizeof (struct challenge_message));
if (!work->conn->use_spnego) {
sz = ksmbd_build_ntlmssp_challenge_blob(chgblob, work->conn);
if (sz < 0)
return -ENOMEM;
rsp->SecurityBufferLength = cpu_to_le16(sz);
return 0;
}
sz = sizeof (struct challenge_message);
sz += (strlen(ksmbd_netbios_name()) * 2 + 1 + 4) * 6;
neg_blob = kzalloc(sz, KSMBD_DEFAULT_GFP);
if (!neg_blob)
return -ENOMEM;
chgblob = (struct challenge_message *)neg_blob;
sz = ksmbd_build_ntlmssp_challenge_blob(chgblob, work->conn);
if (sz < 0) {
rc = -ENOMEM;
goto out;
}
rc = build_spnego_ntlmssp_neg_blob(&spnego_blob, &spnego_blob_len,
neg_blob, sz);
if (rc) {
rc = -ENOMEM;
goto out;
}
memcpy(rsp->Buffer, spnego_blob, spnego_blob_len);
rsp->SecurityBufferLength = cpu_to_le16(spnego_blob_len);
out:
kfree(spnego_blob);
kfree(neg_blob);
return rc;
}
static struct authenticate_message *user_authblob(struct ksmbd_conn *conn,
struct smb2_sess_setup_req *req)
{
int sz;
if (conn->use_spnego && conn->mechToken)
return (struct authenticate_message *)conn->mechToken;
sz = le16_to_cpu(req->SecurityBufferOffset);
return (struct authenticate_message *)((char *)&req->hdr.ProtocolId
+ sz);
}
static struct ksmbd_user *session_user(struct ksmbd_conn *conn,
struct smb2_sess_setup_req *req)
{
struct authenticate_message *authblob;
struct ksmbd_user *user;
char *name;
unsigned int name_off, name_len, secbuf_len;
if (conn->use_spnego && conn->mechToken)
secbuf_len = conn->mechTokenLen;
else
secbuf_len = le16_to_cpu(req->SecurityBufferLength);
if (secbuf_len < sizeof (struct authenticate_message)) {
ksmbd_debug(SMB, "blob len %d too small\n" , secbuf_len);
return NULL;
}
authblob = user_authblob(conn, req);
name_off = le32_to_cpu(authblob->UserName.BufferOffset);
name_len = le16_to_cpu(authblob->UserName.Length);
if (secbuf_len < (u64)name_off + name_len)
return NULL;
name = smb_strndup_from_utf16((const char *)authblob + name_off,
name_len,
true ,
conn->local_nls);
if (IS_ERR(name)) {
pr_err("cannot allocate memory\n" );
return NULL;
}
ksmbd_debug(SMB, "session setup request for user %s\n" , name);
user = ksmbd_login_user(name);
kfree(name);
return user;
}
static int ntlm_authenticate(struct ksmbd_work *work,
struct smb2_sess_setup_req *req,
struct smb2_sess_setup_rsp *rsp)
{
struct ksmbd_conn *conn = work->conn;
struct ksmbd_session *sess = work->sess;
struct channel *chann = NULL, *old;
struct ksmbd_user *user;
u64 prev_id;
int sz, rc;
ksmbd_debug(SMB, "authenticate phase\n" );
if (conn->use_spnego) {
unsigned char *spnego_blob;
u16 spnego_blob_len;
rc = build_spnego_ntlmssp_auth_blob(&spnego_blob,
&spnego_blob_len,
0);
if (rc)
return -ENOMEM;
memcpy(rsp->Buffer, spnego_blob, spnego_blob_len);
rsp->SecurityBufferLength = cpu_to_le16(spnego_blob_len);
kfree(spnego_blob);
}
user = session_user(conn, req);
if (!user) {
ksmbd_debug(SMB, "Unknown user name or an error\n" );
return -EPERM;
}
/* Check for previous session */
prev_id = le64_to_cpu(req->PreviousSessionId);
if (prev_id && prev_id != sess->id)
destroy_previous_session(conn, user, prev_id);
if (sess->state == SMB2_SESSION_VALID) {
/*
* Reuse session if anonymous try to connect
* on reauthetication.
*/
if (conn->binding == false && ksmbd_anonymous_user(user)) {
ksmbd_free_user(user);
return 0;
}
if (!ksmbd_compare_user(sess->user, user)) {
ksmbd_free_user(user);
return -EPERM;
}
ksmbd_free_user(user);
} else {
sess->user = user;
}
if (conn->binding == false && user_guest(sess->user)) {
rsp->SessionFlags = SMB2_SESSION_FLAG_IS_GUEST_LE;
} else {
struct authenticate_message *authblob;
authblob = user_authblob(conn, req);
if (conn->use_spnego && conn->mechToken)
sz = conn->mechTokenLen;
else
sz = le16_to_cpu(req->SecurityBufferLength);
rc = ksmbd_decode_ntlmssp_auth_blob(authblob, sz, conn, sess);
if (rc) {
set_user_flag(sess->user, KSMBD_USER_FLAG_BAD_PASSWORD);
ksmbd_debug(SMB, "authentication failed\n" );
return -EPERM;
}
}
/*
* If session state is SMB2_SESSION_VALID, We can assume
* that it is reauthentication. And the user/password
* has been verified, so return it here.
*/
if (sess->state == SMB2_SESSION_VALID) {
if (conn->binding)
goto binding_session;
return 0;
}
if ((rsp->SessionFlags != SMB2_SESSION_FLAG_IS_GUEST_LE &&
(conn->sign || server_conf.enforced_signing)) ||
(req->SecurityMode & SMB2_NEGOTIATE_SIGNING_REQUIRED))
sess->sign = true ;
if (smb3_encryption_negotiated(conn) &&
!(req->Flags & SMB2_SESSION_REQ_FLAG_BINDING)) {
rc = conn->ops->generate_encryptionkey(conn, sess);
if (rc) {
ksmbd_debug(SMB,
"SMB3 encryption key generation failed\n" );
return -EINVAL;
}
sess->enc = true ;
if (server_conf.flags & KSMBD_GLOBAL_FLAG_SMB2_ENCRYPTION)
rsp->SessionFlags = SMB2_SESSION_FLAG_ENCRYPT_DATA_LE;
/*
* signing is disable if encryption is enable
* on this session
*/
sess->sign = false ;
}
binding_session:
if (conn->dialect >= SMB30_PROT_ID) {
chann = lookup_chann_list(sess, conn);
if (!chann) {
chann = kmalloc(sizeof (struct channel), KSMBD_DEFAULT_GFP);
if (!chann)
return -ENOMEM;
chann->conn = conn;
old = xa_store(&sess->ksmbd_chann_list, (long )conn, chann,
KSMBD_DEFAULT_GFP);
if (xa_is_err(old)) {
kfree(chann);
return xa_err(old);
}
}
}
if (conn->ops->generate_signingkey) {
rc = conn->ops->generate_signingkey(sess, conn);
if (rc) {
ksmbd_debug(SMB, "SMB3 signing key generation failed\n" );
return -EINVAL;
}
}
if (!ksmbd_conn_lookup_dialect(conn)) {
pr_err("fail to verify the dialect\n" );
return -ENOENT;
}
return 0;
}
#ifdef CONFIG_SMB_SERVER_KERBEROS5
static int krb5_authenticate(struct ksmbd_work *work,
struct smb2_sess_setup_req *req,
struct smb2_sess_setup_rsp *rsp)
{
struct ksmbd_conn *conn = work->conn;
struct ksmbd_session *sess = work->sess;
char *in_blob, *out_blob;
struct channel *chann = NULL, *old;
u64 prev_sess_id;
int in_len, out_len;
int retval;
in_blob = (char *)&req->hdr.ProtocolId +
le16_to_cpu(req->SecurityBufferOffset);
in_len = le16_to_cpu(req->SecurityBufferLength);
out_blob = (char *)&rsp->hdr.ProtocolId +
le16_to_cpu(rsp->SecurityBufferOffset);
out_len = work->response_sz -
(le16_to_cpu(rsp->SecurityBufferOffset) + 4);
retval = ksmbd_krb5_authenticate(sess, in_blob, in_len,
out_blob, &out_len);
if (retval) {
ksmbd_debug(SMB, "krb5 authentication failed\n" );
return -EINVAL;
}
/* Check previous session */
prev_sess_id = le64_to_cpu(req->PreviousSessionId);
if (prev_sess_id && prev_sess_id != sess->id)
destroy_previous_session(conn, sess->user, prev_sess_id);
rsp->SecurityBufferLength = cpu_to_le16(out_len);
/*
* If session state is SMB2_SESSION_VALID, We can assume
* that it is reauthentication. And the user/password
* has been verified, so return it here.
*/
if (sess->state == SMB2_SESSION_VALID) {
if (conn->binding)
goto binding_session;
return 0;
}
if ((rsp->SessionFlags != SMB2_SESSION_FLAG_IS_GUEST_LE &&
(conn->sign || server_conf.enforced_signing)) ||
(req->SecurityMode & SMB2_NEGOTIATE_SIGNING_REQUIRED))
sess->sign = true ;
if (smb3_encryption_negotiated(conn) &&
!(req->Flags & SMB2_SESSION_REQ_FLAG_BINDING)) {
retval = conn->ops->generate_encryptionkey(conn, sess);
if (retval) {
ksmbd_debug(SMB,
"SMB3 encryption key generation failed\n" );
return -EINVAL;
}
sess->enc = true ;
if (server_conf.flags & KSMBD_GLOBAL_FLAG_SMB2_ENCRYPTION)
rsp->SessionFlags = SMB2_SESSION_FLAG_ENCRYPT_DATA_LE;
sess->sign = false ;
}
binding_session:
if (conn->dialect >= SMB30_PROT_ID) {
chann = lookup_chann_list(sess, conn);
if (!chann) {
chann = kmalloc(sizeof (struct channel), KSMBD_DEFAULT_GFP);
if (!chann)
return -ENOMEM;
chann->conn = conn;
old = xa_store(&sess->ksmbd_chann_list, (long )conn,
chann, KSMBD_DEFAULT_GFP);
if (xa_is_err(old)) {
kfree(chann);
return xa_err(old);
}
}
}
if (conn->ops->generate_signingkey) {
retval = conn->ops->generate_signingkey(sess, conn);
if (retval) {
ksmbd_debug(SMB, "SMB3 signing key generation failed\n" );
return -EINVAL;
}
}
if (!ksmbd_conn_lookup_dialect(conn)) {
pr_err("fail to verify the dialect\n" );
return -ENOENT;
}
return 0;
}
#else
static int krb5_authenticate(struct ksmbd_work *work,
struct smb2_sess_setup_req *req,
struct smb2_sess_setup_rsp *rsp)
{
return -EOPNOTSUPP;
}
#endif
int smb2_sess_setup(struct ksmbd_work *work)
{
struct ksmbd_conn *conn = work->conn;
struct smb2_sess_setup_req *req;
struct smb2_sess_setup_rsp *rsp;
struct ksmbd_session *sess;
struct negotiate_message *negblob;
unsigned int negblob_len, negblob_off;
int rc = 0;
ksmbd_debug(SMB, "Received smb2 session setup request\n" );
if (!ksmbd_conn_need_setup(conn) && !ksmbd_conn_good(conn)) {
work->send_no_response = 1;
return rc;
}
WORK_BUFFERS(work, req, rsp);
rsp->StructureSize = cpu_to_le16(9);
rsp->SessionFlags = 0;
rsp->SecurityBufferOffset = cpu_to_le16(72);
rsp->SecurityBufferLength = 0;
ksmbd_conn_lock(conn);
if (!req->hdr.SessionId) {
sess = ksmbd_smb2_session_create();
if (!sess) {
rc = -ENOMEM;
goto out_err;
}
rsp->hdr.SessionId = cpu_to_le64(sess->id);
rc = ksmbd_session_register(conn, sess);
if (rc)
goto out_err;
conn->binding = false ;
} else if (conn->dialect >= SMB30_PROT_ID &&
(server_conf.flags & KSMBD_GLOBAL_FLAG_SMB3_MULTICHANNEL) &&
req->Flags & SMB2_SESSION_REQ_FLAG_BINDING) {
u64 sess_id = le64_to_cpu(req->hdr.SessionId);
sess = ksmbd_session_lookup_slowpath(sess_id);
if (!sess) {
rc = -ENOENT;
goto out_err;
}
if (conn->dialect != sess->dialect) {
rc = -EINVAL;
goto out_err;
}
if (!(req->hdr.Flags & SMB2_FLAGS_SIGNED)) {
rc = -EINVAL;
goto out_err;
}
if (strncmp(conn->ClientGUID, sess->ClientGUID,
SMB2_CLIENT_GUID_SIZE)) {
rc = -ENOENT;
goto out_err;
}
if (sess->state == SMB2_SESSION_IN_PROGRESS) {
rc = -EACCES;
goto out_err;
}
if (sess->state == SMB2_SESSION_EXPIRED) {
rc = -EFAULT;
goto out_err;
}
if (ksmbd_conn_need_reconnect(conn)) {
rc = -EFAULT;
ksmbd_user_session_put(sess);
sess = NULL;
goto out_err;
}
if (is_ksmbd_session_in_connection(conn, sess_id)) {
rc = -EACCES;
goto out_err;
}
if (user_guest(sess->user)) {
rc = -EOPNOTSUPP;
goto out_err;
}
conn->binding = true ;
} else if ((conn->dialect < SMB30_PROT_ID ||
server_conf.flags & KSMBD_GLOBAL_FLAG_SMB3_MULTICHANNEL) &&
(req->Flags & SMB2_SESSION_REQ_FLAG_BINDING)) {
sess = NULL;
rc = -EACCES;
goto out_err;
} else {
sess = ksmbd_session_lookup(conn,
le64_to_cpu(req->hdr.SessionId));
if (!sess) {
rc = -ENOENT;
goto out_err;
}
if (sess->state == SMB2_SESSION_EXPIRED) {
rc = -EFAULT;
goto out_err;
}
if (ksmbd_conn_need_reconnect(conn)) {
rc = -EFAULT;
ksmbd_user_session_put(sess);
sess = NULL;
goto out_err;
}
conn->binding = false ;
}
work->sess = sess;
negblob_off = le16_to_cpu(req->SecurityBufferOffset);
negblob_len = le16_to_cpu(req->SecurityBufferLength);
if (negblob_off < offsetof(struct smb2_sess_setup_req, Buffer)) {
rc = -EINVAL;
goto out_err;
}
negblob = (struct negotiate_message *)((char *)&req->hdr.ProtocolId +
negblob_off);
if (decode_negotiation_token(conn, negblob, negblob_len) == 0) {
if (conn->mechToken) {
negblob = (struct negotiate_message *)conn->mechToken;
negblob_len = conn->mechTokenLen;
}
}
if (negblob_len < offsetof(struct negotiate_message, NegotiateFlags)) {
rc = -EINVAL;
goto out_err;
}
if (server_conf.auth_mechs & conn->auth_mechs) {
rc = generate_preauth_hash(work);
if (rc)
goto out_err;
if (conn->preferred_auth_mech &
(KSMBD_AUTH_KRB5 | KSMBD_AUTH_MSKRB5)) {
rc = krb5_authenticate(work, req, rsp);
if (rc) {
rc = -EINVAL;
goto out_err;
}
if (!ksmbd_conn_need_reconnect(conn)) {
ksmbd_conn_set_good(conn);
sess->state = SMB2_SESSION_VALID;
}
} else if (conn->preferred_auth_mech == KSMBD_AUTH_NTLMSSP) {
if (negblob->MessageType == NtLmNegotiate) {
rc = ntlm_negotiate(work, negblob, negblob_len, rsp);
if (rc)
goto out_err;
rsp->hdr.Status =
STATUS_MORE_PROCESSING_REQUIRED;
} else if (negblob->MessageType == NtLmAuthenticate) {
rc = ntlm_authenticate(work, req, rsp);
if (rc)
goto out_err;
if (!ksmbd_conn_need_reconnect(conn)) {
ksmbd_conn_set_good(conn);
sess->state = SMB2_SESSION_VALID;
}
if (conn->binding) {
struct preauth_session *preauth_sess;
preauth_sess =
ksmbd_preauth_session_lookup(conn, sess->id);
if (preauth_sess) {
list_del(&preauth_sess->preauth_entry);
kfree(preauth_sess);
}
}
} else {
pr_info_ratelimited("Unknown NTLMSSP message type : 0x%x\n" ,
le32_to_cpu(negblob->MessageType));
rc = -EINVAL;
}
} else {
/* TODO: need one more negotiation */
pr_err("Not support the preferred authentication\n" );
rc = -EINVAL;
}
} else {
pr_err("Not support authentication\n" );
rc = -EINVAL;
}
out_err:
if (rc == -EINVAL)
rsp->hdr.Status = STATUS_INVALID_PARAMETER;
else if (rc == -ENOENT)
rsp->hdr.Status = STATUS_USER_SESSION_DELETED;
else if (rc == -EACCES)
rsp->hdr.Status = STATUS_REQUEST_NOT_ACCEPTED;
else if (rc == -EFAULT)
rsp->hdr.Status = STATUS_NETWORK_SESSION_EXPIRED;
else if (rc == -ENOMEM)
rsp->hdr.Status = STATUS_INSUFFICIENT_RESOURCES;
else if (rc == -EOPNOTSUPP)
rsp->hdr.Status = STATUS_NOT_SUPPORTED;
else if (rc)
rsp->hdr.Status = STATUS_LOGON_FAILURE;
if (conn->use_spnego && conn->mechToken) {
kfree(conn->mechToken);
conn->mechToken = NULL;
}
if (rc < 0) {
/*
* SecurityBufferOffset should be set to zero
* in session setup error response.
*/
rsp->SecurityBufferOffset = 0;
if (sess) {
bool try_delay = false ;
/*
* To avoid dictionary attacks (repeated session setups rapidly sent) to
* connect to server, ksmbd make a delay of a 5 seconds on session setup
* failure to make it harder to send enough random connection requests
* to break into a server.
*/
if (sess->user && sess->user->flags & KSMBD_USER_FLAG_DELAY_SESSION)
try_delay = true ;
sess->last_active = jiffies;
sess->state = SMB2_SESSION_EXPIRED;
ksmbd_user_session_put(sess);
work->sess = NULL;
if (try_delay) {
ksmbd_conn_set_need_reconnect(conn);
ssleep(5);
ksmbd_conn_set_need_setup(conn);
}
}
smb2_set_err_rsp(work);
} else {
unsigned int iov_len;
if (rsp->SecurityBufferLength)
iov_len = offsetof(struct smb2_sess_setup_rsp, Buffer) +
le16_to_cpu(rsp->SecurityBufferLength);
else
iov_len = sizeof (struct smb2_sess_setup_rsp);
rc = ksmbd_iov_pin_rsp(work, rsp, iov_len);
if (rc)
rsp->hdr.Status = STATUS_INSUFFICIENT_RESOURCES;
}
ksmbd_conn_unlock(conn);
return rc;
}
/**
* smb2_tree_connect() - handler for smb2 tree connect command
* @work: smb work containing smb request buffer
*
* Return: 0 on success, otherwise error
*/
int smb2_tree_connect(struct ksmbd_work *work)
{
struct ksmbd_conn *conn = work->conn;
struct smb2_tree_connect_req *req;
struct smb2_tree_connect_rsp *rsp;
struct ksmbd_session *sess = work->sess;
char *treename = NULL, *name = NULL;
struct ksmbd_tree_conn_status status;
struct ksmbd_share_config *share = NULL;
int rc = -EINVAL;
ksmbd_debug(SMB, "Received smb2 tree connect request\n" );
WORK_BUFFERS(work, req, rsp);
treename = smb_strndup_from_utf16((char *)req + le16_to_cpu(req->PathOffset),
le16_to_cpu(req->PathLength), true ,
conn->local_nls);
if (IS_ERR(treename)) {
pr_err("treename is NULL\n" );
status.ret = KSMBD_TREE_CONN_STATUS_ERROR;
goto out_err1;
}
name = ksmbd_extract_sharename(conn->um, treename);
if (IS_ERR(name)) {
status.ret = KSMBD_TREE_CONN_STATUS_ERROR;
goto out_err1;
}
ksmbd_debug(SMB, "tree connect request for tree %s treename %s\n" ,
name, treename);
status = ksmbd_tree_conn_connect(work, name);
if (status.ret == KSMBD_TREE_CONN_STATUS_OK)
rsp->hdr.Id.SyncId.TreeId = cpu_to_le32(status.tree_conn->id);
else
goto out_err1;
share = status.tree_conn->share_conf;
if (test_share_config_flag(share, KSMBD_SHARE_FLAG_PIPE)) {
ksmbd_debug(SMB, "IPC share path request\n" );
rsp->ShareType = SMB2_SHARE_TYPE_PIPE;
rsp->MaximalAccess = FILE_READ_DATA_LE | FILE_READ_EA_LE |
FILE_EXECUTE_LE | FILE_READ_ATTRIBUTES_LE |
FILE_DELETE_LE | FILE_READ_CONTROL_LE |
FILE_WRITE_DAC_LE | FILE_WRITE_OWNER_LE |
FILE_SYNCHRONIZE_LE;
} else {
rsp->ShareType = SMB2_SHARE_TYPE_DISK;
rsp->MaximalAccess = FILE_READ_DATA_LE | FILE_READ_EA_LE |
FILE_EXECUTE_LE | FILE_READ_ATTRIBUTES_LE;
if (test_tree_conn_flag(status.tree_conn,
KSMBD_TREE_CONN_FLAG_WRITABLE)) {
rsp->MaximalAccess |= FILE_WRITE_DATA_LE |
FILE_APPEND_DATA_LE | FILE_WRITE_EA_LE |
FILE_DELETE_LE | FILE_WRITE_ATTRIBUTES_LE |
FILE_DELETE_CHILD_LE | FILE_READ_CONTROL_LE |
FILE_WRITE_DAC_LE | FILE_WRITE_OWNER_LE |
FILE_SYNCHRONIZE_LE;
}
}
status.tree_conn->maximal_access = le32_to_cpu(rsp->MaximalAccess);
if (conn->posix_ext_supported)
status.tree_conn->posix_extensions = true ;
write_lock(&sess->tree_conns_lock);
status.tree_conn->t_state = TREE_CONNECTED;
write_unlock(&sess->tree_conns_lock);
rsp->StructureSize = cpu_to_le16(16);
out_err1:
if (server_conf.flags & KSMBD_GLOBAL_FLAG_DURABLE_HANDLE && share &&
test_share_config_flag(share,
KSMBD_SHARE_FLAG_CONTINUOUS_AVAILABILITY))
rsp->Capabilities = SMB2_SHARE_CAP_CONTINUOUS_AVAILABILITY;
else
rsp->Capabilities = 0;
rsp->Reserved = 0;
/* default manual caching */
rsp->ShareFlags = SMB2_SHAREFLAG_MANUAL_CACHING;
rc = ksmbd_iov_pin_rsp(work, rsp, sizeof (struct smb2_tree_connect_rsp));
if (rc)
status.ret = KSMBD_TREE_CONN_STATUS_NOMEM;
if (!IS_ERR(treename))
kfree(treename);
if (!IS_ERR(name))
kfree(name);
switch (status.ret) {
case KSMBD_TREE_CONN_STATUS_OK:
rsp->hdr.Status = STATUS_SUCCESS;
rc = 0;
break ;
case -ESTALE:
case -ENOENT:
case KSMBD_TREE_CONN_STATUS_NO_SHARE:
rsp->hdr.Status = STATUS_BAD_NETWORK_NAME;
break ;
case -ENOMEM:
case KSMBD_TREE_CONN_STATUS_NOMEM:
rsp->hdr.Status = STATUS_NO_MEMORY;
break ;
case KSMBD_TREE_CONN_STATUS_ERROR:
case KSMBD_TREE_CONN_STATUS_TOO_MANY_CONNS:
case KSMBD_TREE_CONN_STATUS_TOO_MANY_SESSIONS:
rsp->hdr.Status = STATUS_ACCESS_DENIED;
break ;
case -EINVAL:
rsp->hdr.Status = STATUS_INVALID_PARAMETER;
break ;
default :
rsp->hdr.Status = STATUS_ACCESS_DENIED;
}
if (status.ret != KSMBD_TREE_CONN_STATUS_OK)
smb2_set_err_rsp(work);
return rc;
}
/**
* smb2_create_open_flags() - convert smb open flags to unix open flags
* @file_present: is file already present
* @access: file access flags
* @disposition: file disposition flags
* @may_flags: set with MAY_ flags
* @coptions: file creation options
* @mode: file mode
*
* Return: file open flags
*/
static int smb2_create_open_flags(bool file_present, __le32 access,
__le32 disposition,
int *may_flags,
__le32 coptions,
umode_t mode)
{
int oflags = O_NONBLOCK | O_LARGEFILE;
if (coptions & FILE_DIRECTORY_FILE_LE || S_ISDIR(mode)) {
access &= ~FILE_WRITE_DESIRE_ACCESS_LE;
ksmbd_debug(SMB, "Discard write access to a directory\n" );
}
if (access & FILE_READ_DESIRED_ACCESS_LE &&
access & FILE_WRITE_DESIRE_ACCESS_LE) {
oflags |= O_RDWR;
*may_flags = MAY_OPEN | MAY_READ | MAY_WRITE;
} else if (access & FILE_WRITE_DESIRE_ACCESS_LE) {
oflags |= O_WRONLY;
*may_flags = MAY_OPEN | MAY_WRITE;
} else {
oflags |= O_RDONLY;
*may_flags = MAY_OPEN | MAY_READ;
}
if (access == FILE_READ_ATTRIBUTES_LE || S_ISBLK(mode) || S_ISCHR(mode))
oflags |= O_PATH;
if (file_present) {
switch (disposition & FILE_CREATE_MASK_LE) {
case FILE_OPEN_LE:
case FILE_CREATE_LE:
break ;
case FILE_SUPERSEDE_LE:
case FILE_OVERWRITE_LE:
case FILE_OVERWRITE_IF_LE:
oflags |= O_TRUNC;
break ;
default :
break ;
}
} else {
switch (disposition & FILE_CREATE_MASK_LE) {
case FILE_SUPERSEDE_LE:
case FILE_CREATE_LE:
case FILE_OPEN_IF_LE:
case FILE_OVERWRITE_IF_LE:
oflags |= O_CREAT;
break ;
case FILE_OPEN_LE:
case FILE_OVERWRITE_LE:
oflags &= ~O_CREAT;
break ;
default :
break ;
}
}
return oflags;
}
/**
* smb2_tree_disconnect() - handler for smb tree connect request
* @work: smb work containing request buffer
*
* Return: 0
*/
int smb2_tree_disconnect(struct ksmbd_work *work)
{
struct smb2_tree_disconnect_rsp *rsp;
struct smb2_tree_disconnect_req *req;
struct ksmbd_session *sess = work->sess;
struct ksmbd_tree_connect *tcon = work->tcon;
int err;
ksmbd_debug(SMB, "Received smb2 tree disconnect request\n" );
WORK_BUFFERS(work, req, rsp);
if (!tcon) {
ksmbd_debug(SMB, "Invalid tid %d\n" , req->hdr.Id.SyncId.TreeId);
rsp->hdr.Status = STATUS_NETWORK_NAME_DELETED;
err = -ENOENT;
goto err_out;
}
ksmbd_close_tree_conn_fds(work);
write_lock(&sess->tree_conns_lock);
if (tcon->t_state == TREE_DISCONNECTED) {
write_unlock(&sess->tree_conns_lock);
rsp->hdr.Status = STATUS_NETWORK_NAME_DELETED;
err = -ENOENT;
goto err_out;
}
WARN_ON_ONCE(atomic_dec_and_test(&tcon->refcount));
tcon->t_state = TREE_DISCONNECTED;
write_unlock(&sess->tree_conns_lock);
err = ksmbd_tree_conn_disconnect(sess, tcon);
if (err) {
rsp->hdr.Status = STATUS_NETWORK_NAME_DELETED;
goto err_out;
}
work->tcon = NULL;
rsp->StructureSize = cpu_to_le16(4);
err = ksmbd_iov_pin_rsp(work, rsp,
sizeof (struct smb2_tree_disconnect_rsp));
if (err) {
rsp->hdr.Status = STATUS_INSUFFICIENT_RESOURCES;
goto err_out;
}
return 0;
err_out:
smb2_set_err_rsp(work);
return err;
}
/**
* smb2_session_logoff() - handler for session log off request
* @work: smb work containing request buffer
*
* Return: 0
*/
int smb2_session_logoff(struct ksmbd_work *work)
{
struct ksmbd_conn *conn = work->conn;
struct ksmbd_session *sess = work->sess;
struct smb2_logoff_req *req;
struct smb2_logoff_rsp *rsp;
u64 sess_id;
int err;
WORK_BUFFERS(work, req, rsp);
ksmbd_debug(SMB, "Received smb2 session logoff request\n" );
ksmbd_conn_lock(conn);
if (!ksmbd_conn_good(conn)) {
ksmbd_conn_unlock(conn);
rsp->hdr.Status = STATUS_NETWORK_NAME_DELETED;
smb2_set_err_rsp(work);
return -ENOENT;
}
sess_id = le64_to_cpu(req->hdr.SessionId);
ksmbd_all_conn_set_status(sess_id, KSMBD_SESS_NEED_RECONNECT);
ksmbd_conn_unlock(conn);
ksmbd_close_session_fds(work);
ksmbd_conn_wait_idle(conn);
if (ksmbd_tree_conn_session_logoff(sess)) {
ksmbd_debug(SMB, "Invalid tid %d\n" , req->hdr.Id.SyncId.TreeId);
rsp->hdr.Status = STATUS_NETWORK_NAME_DELETED;
smb2_set_err_rsp(work);
return -ENOENT;
}
down_write(&conn->session_lock);
sess->state = SMB2_SESSION_EXPIRED;
up_write(&conn->session_lock);
ksmbd_all_conn_set_status(sess_id, KSMBD_SESS_NEED_SETUP);
rsp->StructureSize = cpu_to_le16(4);
err = ksmbd_iov_pin_rsp(work, rsp, sizeof (struct smb2_logoff_rsp));
if (err) {
rsp->hdr.Status = STATUS_INSUFFICIENT_RESOURCES;
smb2_set_err_rsp(work);
return err;
}
return 0;
}
/**
* create_smb2_pipe() - create IPC pipe
* @work: smb work containing request buffer
*
* Return: 0 on success, otherwise error
*/
static noinline int create_smb2_pipe(struct ksmbd_work *work)
{
struct smb2_create_rsp *rsp;
struct smb2_create_req *req;
int id;
int err;
char *name;
WORK_BUFFERS(work, req, rsp);
name = smb_strndup_from_utf16(req->Buffer, le16_to_cpu(req->NameLength),
1, work->conn->local_nls);
if (IS_ERR(name)) {
rsp->hdr.Status = STATUS_NO_MEMORY;
err = PTR_ERR(name);
goto out;
}
id = ksmbd_session_rpc_open(work->sess, name);
if (id < 0) {
pr_err("Unable to open RPC pipe: %d\n" , id);
err = id;
goto out;
}
rsp->hdr.Status = STATUS_SUCCESS;
rsp->StructureSize = cpu_to_le16(89);
rsp->OplockLevel = SMB2_OPLOCK_LEVEL_NONE;
rsp->Flags = 0;
rsp->CreateAction = cpu_to_le32(FILE_OPENED);
rsp->CreationTime = cpu_to_le64(0);
rsp->LastAccessTime = cpu_to_le64(0);
rsp->ChangeTime = cpu_to_le64(0);
rsp->AllocationSize = cpu_to_le64(0);
rsp->EndofFile = cpu_to_le64(0);
rsp->FileAttributes = FILE_ATTRIBUTE_NORMAL_LE;
rsp->Reserved2 = 0;
rsp->VolatileFileId = id;
rsp->PersistentFileId = 0;
rsp->CreateContextsOffset = 0;
rsp->CreateContextsLength = 0;
err = ksmbd_iov_pin_rsp(work, rsp, offsetof(struct smb2_create_rsp, Buffer));
if (err)
goto out;
kfree(name);
return 0;
out:
switch (err) {
case -EINVAL:
rsp->hdr.Status = STATUS_INVALID_PARAMETER;
break ;
case -ENOSPC:
case -ENOMEM:
rsp->hdr.Status = STATUS_NO_MEMORY;
break ;
}
if (!IS_ERR(name))
kfree(name);
smb2_set_err_rsp(work);
return err;
}
/**
* smb2_set_ea() - handler for setting extended attributes using set
* info command
* @eabuf: set info command buffer
* @buf_len: set info command buffer length
* @path: dentry path for get ea
* @get_write: get write access to a mount
*
* Return: 0 on success, otherwise error
*/
static int smb2_set_ea(struct smb2_ea_info *eabuf, unsigned int buf_len,
const struct path *path, bool get_write)
{
struct mnt_idmap *idmap = mnt_idmap(path->mnt);
char *attr_name = NULL, *value;
int rc = 0;
unsigned int next = 0;
if (buf_len < sizeof (struct smb2_ea_info) + eabuf->EaNameLength +
le16_to_cpu(eabuf->EaValueLength))
return -EINVAL;
attr_name = kmalloc(XATTR_NAME_MAX + 1, KSMBD_DEFAULT_GFP);
if (!attr_name)
return -ENOMEM;
do {
if (!eabuf->EaNameLength)
goto next;
ksmbd_debug(SMB,
"name : <%s>, name_len : %u, value_len : %u, next : %u\n" ,
eabuf->name, eabuf->EaNameLength,
le16_to_cpu(eabuf->EaValueLength),
le32_to_cpu(eabuf->NextEntryOffset));
if (eabuf->EaNameLength >
(XATTR_NAME_MAX - XATTR_USER_PREFIX_LEN)) {
rc = -EINVAL;
break ;
}
memcpy(attr_name, XATTR_USER_PREFIX, XATTR_USER_PREFIX_LEN);
memcpy(&attr_name[XATTR_USER_PREFIX_LEN], eabuf->name,
eabuf->EaNameLength);
attr_name[XATTR_USER_PREFIX_LEN + eabuf->EaNameLength] = '\0' ;
value = (char *)&eabuf->name + eabuf->EaNameLength + 1;
if (!eabuf->EaValueLength) {
rc = ksmbd_vfs_casexattr_len(idmap,
path->dentry,
attr_name,
XATTR_USER_PREFIX_LEN +
eabuf->EaNameLength);
/* delete the EA only when it exits */
if (rc > 0) {
rc = ksmbd_vfs_remove_xattr(idmap,
path,
attr_name,
get_write);
if (rc < 0) {
ksmbd_debug(SMB,
"remove xattr failed(%d)\n" ,
rc);
break ;
}
}
/* if the EA doesn't exist, just do nothing. */
rc = 0;
} else {
rc = ksmbd_vfs_setxattr(idmap, path, attr_name, value,
le16_to_cpu(eabuf->EaValueLength),
0, get_write);
if (rc < 0) {
ksmbd_debug(SMB,
"ksmbd_vfs_setxattr is failed(%d)\n" ,
--> --------------------
--> maximum size reached
--> --------------------
Messung V0.5 C=96 H=92 G=93
¤ Dauer der Verarbeitung: 0.23 Sekunden
¤
*© Formatika GbR, Deutschland