Quellcodebibliothek Statistik Leitseite products/sources/formale Sprachen/C/Linux/fs/smb/server/   (Open Source Betriebssystem Version 6.17.9©)  Datei vom 24.10.2025 mit Größe 240 kB image not shown  

Quelle  smb2pdu.c   Sprache: C

 
// 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






Wurzel

Suchen

Beweissystem der NASA

Beweissystem Isabelle

NIST Cobol Testsuite

Cephes Mathematical Library

Wiener Entwicklungsmethode

Haftungshinweis

Die Informationen auf dieser Webseite wurden nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit, noch Qualität der bereit gestellten Informationen zugesichert.

Bemerkung:

Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.