Anforderungen  |   Konzepte  |   Entwurf  |   Entwicklung  |   Qualitätssicherung  |   Lebenszyklus  |   Steuerung
 
 
 
 


Quelle  smb2pdu.c   Sprache: C

 
// SPDX-License-Identifier: LGPL-2.1
/*
 *
 *   Copyright (C) International Business Machines  Corp., 2009, 2013
 *                 Etersoft, 2012
 *   Author(s): Steve French (sfrench@us.ibm.com)
 *              Pavel Shilovsky (pshilovsky@samba.org) 2012
 *
 *   Contains the routines for constructing the SMB2 PDUs themselves
 *
 */


 /* SMB2 PDU handling routines here - except for leftovers (eg session setup) */
 /* Note that there are handle based routines which must be       */
 /* treated slightly differently for reconnection purposes since we never     */
 /* want to reuse a stale file handle and only the caller knows the file info */

#include <linux/fs.h>
#include <linux/kernel.h>
#include <linux/vfs.h>
#include <linux/task_io_accounting_ops.h>
#include <linux/uaccess.h>
#include <linux/uuid.h>
#include <linux/pagemap.h>
#include <linux/xattr.h>
#include <linux/netfs.h>
#include <trace/events/netfs.h>
#include "cifsglob.h"
#include "cifsacl.h"
#include "cifsproto.h"
#include "smb2proto.h"
#include "cifs_unicode.h"
#include "cifs_debug.h"
#include "ntlmssp.h"
#include "../common/smb2status.h"
#include "smb2glob.h"
#include "cifspdu.h"
#include "cifs_spnego.h"
#include "../common/smbdirect/smbdirect.h"
#include "smbdirect.h"
#include "trace.h"
#ifdef CONFIG_CIFS_DFS_UPCALL
#include "dfs_cache.h"
#endif
#include "cached_dir.h"
#include "compress.h"
#include "fs_context.h"

/*
 *  The following table defines the expected "StructureSize" of SMB2 requests
 *  in order by SMB2 command.  This is similar to "wct" in SMB/CIFS requests.
 *
 *  Note that commands are defined in smb2pdu.h in le16 but the array below is
 *  indexed by command in host byte order.
 */

static const int smb2_req_struct_sizes[NUMBER_OF_SMB2_COMMANDS] = {
 /* SMB2_NEGOTIATE */ 36,
 /* SMB2_SESSION_SETUP */ 25,
 /* SMB2_LOGOFF */ 4,
 /* SMB2_TREE_CONNECT */ 9,
 /* SMB2_TREE_DISCONNECT */ 4,
 /* SMB2_CREATE */ 57,
 /* SMB2_CLOSE */ 24,
 /* SMB2_FLUSH */ 24,
 /* SMB2_READ */ 49,
 /* SMB2_WRITE */ 49,
 /* SMB2_LOCK */ 48,
 /* SMB2_IOCTL */ 57,
 /* SMB2_CANCEL */ 4,
 /* SMB2_ECHO */ 4,
 /* SMB2_QUERY_DIRECTORY */ 33,
 /* SMB2_CHANGE_NOTIFY */ 32,
 /* SMB2_QUERY_INFO */ 41,
 /* SMB2_SET_INFO */ 33,
 /* SMB2_OPLOCK_BREAK */ 24 /* BB this is 36 for LEASE_BREAK variant */
};

int smb3_encryption_required(const struct cifs_tcon *tcon)
{
 if (!tcon || !tcon->ses)
  return 0;
 if ((tcon->ses->session_flags & SMB2_SESSION_FLAG_ENCRYPT_DATA) ||
     (tcon->share_flags & SHI1005_FLAGS_ENCRYPT_DATA))
  return 1;
 if (tcon->seal &&
     (tcon->ses->server->capabilities & SMB2_GLOBAL_CAP_ENCRYPTION))
  return 1;
 if (((global_secflags & CIFSSEC_MUST_SEAL) == CIFSSEC_MUST_SEAL) &&
     (tcon->ses->server->capabilities & SMB2_GLOBAL_CAP_ENCRYPTION))
  return 1;
 return 0;
}

static void
smb2_hdr_assemble(struct smb2_hdr *shdr, __le16 smb2_cmd,
    const struct cifs_tcon *tcon,
    struct TCP_Server_Info *server)
{
 struct smb3_hdr_req *smb3_hdr;

 shdr->ProtocolId = SMB2_PROTO_NUMBER;
 shdr->StructureSize = cpu_to_le16(64);
 shdr->Command = smb2_cmd;

 if (server) {
  /* After reconnect SMB3 must set ChannelSequence on subsequent reqs */
  if (server->dialect >= SMB30_PROT_ID) {
   smb3_hdr = (struct smb3_hdr_req *)shdr;
   /*
 * if primary channel is not set yet, use default
 * channel for chan sequence num
 */

   if (SERVER_IS_CHAN(server))
    smb3_hdr->ChannelSequence =
     cpu_to_le16(server->primary_server->channel_sequence_num);
   else
    smb3_hdr->ChannelSequence =
     cpu_to_le16(server->channel_sequence_num);
  }
  spin_lock(&server->req_lock);
  /* Request up to 10 credits but don't go over the limit. */
  if (server->credits >= server->max_credits)
   shdr->CreditRequest = cpu_to_le16(0);
  else
   shdr->CreditRequest = cpu_to_le16(
    min_t(int, server->max_credits -
      server->credits, 10));
  spin_unlock(&server->req_lock);
 } else {
  shdr->CreditRequest = cpu_to_le16(2);
 }
 shdr->Id.SyncId.ProcessId = cpu_to_le32((__u16)current->tgid);

 if (!tcon)
  goto out;

 /* GLOBAL_CAP_LARGE_MTU will only be set if dialect > SMB2.02 */
 /* See sections 2.2.4 and 3.2.4.1.5 of MS-SMB2 */
 if (server && (server->capabilities & SMB2_GLOBAL_CAP_LARGE_MTU))
  shdr->CreditCharge = cpu_to_le16(1);
 /* else CreditCharge MBZ */

 shdr->Id.SyncId.TreeId = cpu_to_le32(tcon->tid);
 /* Uid is not converted */
 if (tcon->ses)
  shdr->SessionId = cpu_to_le64(tcon->ses->Suid);

 /*
 * If we would set SMB2_FLAGS_DFS_OPERATIONS on open we also would have
 * to pass the path on the Open SMB prefixed by \\server\share.
 * Not sure when we would need to do the augmented path (if ever) and
 * setting this flag breaks the SMB2 open operation since it is
 * illegal to send an empty path name (without \\server\share prefix)
 * when the DFS flag is set in the SMB open header. We could
 * consider setting the flag on all operations other than open
 * but it is safer to net set it for now.
 */

/* if (tcon->share_flags & SHI1005_FLAGS_DFS)
shdr->Flags |= SMB2_FLAGS_DFS_OPERATIONS; */


 if (server && server->sign && !smb3_encryption_required(tcon))
  shdr->Flags |= SMB2_FLAGS_SIGNED;
out:
 return;
}

/* helper function for code reuse */
static int
cifs_chan_skip_or_disable(struct cifs_ses *ses,
     struct TCP_Server_Info *server,
     bool from_reconnect)
{
 struct TCP_Server_Info *pserver;
 unsigned int chan_index;

 if (SERVER_IS_CHAN(server)) {
  cifs_dbg(VFS,
   "server %s does not support multichannel anymore. Skip secondary channel\n",
    ses->server->hostname);

  spin_lock(&ses->chan_lock);
  chan_index = cifs_ses_get_chan_index(ses, server);
  if (chan_index == CIFS_INVAL_CHAN_INDEX) {
   spin_unlock(&ses->chan_lock);
   goto skip_terminate;
  }

  ses->chans[chan_index].server = NULL;
  server->terminate = true;
  spin_unlock(&ses->chan_lock);

  /*
 * the above reference of server by channel
 * needs to be dropped without holding chan_lock
 * as cifs_put_tcp_session takes a higher lock
 * i.e. cifs_tcp_ses_lock
 */

  cifs_put_tcp_session(server, from_reconnect);

  cifs_signal_cifsd_for_reconnect(server, false);

  /* mark primary server as needing reconnect */
  pserver = server->primary_server;
  cifs_signal_cifsd_for_reconnect(pserver, false);
skip_terminate:
  return -EHOSTDOWN;
 }

 cifs_server_dbg(VFS,
  "server does not support multichannel anymore. Disable all other channels\n");
 cifs_disable_secondary_channels(ses);


 return 0;
}

static int
smb2_reconnect(__le16 smb2_command, struct cifs_tcon *tcon,
        struct TCP_Server_Info *server, bool from_reconnect)
{
 struct cifs_ses *ses;
 int xid;
 int rc = 0;

 /*
 * SMB2s NegProt, SessSetup, Logoff do not have tcon yet so
 * check for tcp and smb session status done differently
 * for those three - in the calling routine.
 */

 if (tcon == NULL)
  return 0;

 if (smb2_command == SMB2_TREE_CONNECT)
  return 0;

 spin_lock(&tcon->tc_lock);
 if (tcon->status == TID_EXITING) {
  /*
 * only tree disconnect allowed when disconnecting ...
 */

  if (smb2_command != SMB2_TREE_DISCONNECT) {
   spin_unlock(&tcon->tc_lock);
   cifs_dbg(FYI, "can not send cmd %d while umounting\n",
     smb2_command);
   return -ENODEV;
  }
 }
 spin_unlock(&tcon->tc_lock);

 ses = tcon->ses;
 if (!ses)
  return -EIO;
 spin_lock(&ses->ses_lock);
 if (ses->ses_status == SES_EXITING) {
  spin_unlock(&ses->ses_lock);
  return -EIO;
 }
 spin_unlock(&ses->ses_lock);
 if (!ses->server || !server)
  return -EIO;

 spin_lock(&server->srv_lock);
 if (server->tcpStatus == CifsNeedReconnect) {
  /*
 * Return to caller for TREE_DISCONNECT and LOGOFF and CLOSE
 * here since they are implicitly done when session drops.
 */

  switch (smb2_command) {
  /*
 * BB Should we keep oplock break and add flush to exceptions?
 */

  case SMB2_TREE_DISCONNECT:
  case SMB2_CANCEL:
  case SMB2_CLOSE:
  case SMB2_OPLOCK_BREAK:
   spin_unlock(&server->srv_lock);
   return -EAGAIN;
  }
 }

 /* if server is marked for termination, cifsd will cleanup */
 if (server->terminate) {
  spin_unlock(&server->srv_lock);
  return -EHOSTDOWN;
 }
 spin_unlock(&server->srv_lock);

again:
 rc = cifs_wait_for_server_reconnect(server, tcon->retry);
 if (rc)
  return rc;

 spin_lock(&ses->chan_lock);
 if (!cifs_chan_needs_reconnect(ses, server) && !tcon->need_reconnect) {
  spin_unlock(&ses->chan_lock);
  return 0;
 }
 spin_unlock(&ses->chan_lock);
 cifs_dbg(FYI, "sess reconnect mask: 0x%lx, tcon reconnect: %d",
   tcon->ses->chans_need_reconnect,
   tcon->need_reconnect);

 mutex_lock(&ses->session_mutex);
 /*
 * Handle the case where a concurrent thread failed to negotiate or
 * killed a channel.
 */

 spin_lock(&server->srv_lock);
 switch (server->tcpStatus) {
 case CifsExiting:
  spin_unlock(&server->srv_lock);
  mutex_unlock(&ses->session_mutex);
  return -EHOSTDOWN;
 case CifsNeedReconnect:
  spin_unlock(&server->srv_lock);
  mutex_unlock(&ses->session_mutex);
  if (!tcon->retry)
   return -EHOSTDOWN;
  goto again;
 default:
  break;
 }
 spin_unlock(&server->srv_lock);

 /*
 * need to prevent multiple threads trying to simultaneously
 * reconnect the same SMB session
 */

 spin_lock(&ses->ses_lock);
 spin_lock(&ses->chan_lock);
 if (!cifs_chan_needs_reconnect(ses, server) &&
     ses->ses_status == SES_GOOD) {
  spin_unlock(&ses->chan_lock);
  spin_unlock(&ses->ses_lock);
  /* this means that we only need to tree connect */
  if (tcon->need_reconnect)
   goto skip_sess_setup;

  mutex_unlock(&ses->session_mutex);
  goto out;
 }
 spin_unlock(&ses->chan_lock);
 spin_unlock(&ses->ses_lock);

 rc = cifs_negotiate_protocol(0, ses, server);
 if (rc) {
  mutex_unlock(&ses->session_mutex);
  if (!tcon->retry)
   return -EHOSTDOWN;
  goto again;
 }
 /*
 * if server stopped supporting multichannel
 * and the first channel reconnected, disable all the others.
 */

 if (ses->chan_count > 1 &&
     !(server->capabilities & SMB2_GLOBAL_CAP_MULTI_CHANNEL)) {
  rc = cifs_chan_skip_or_disable(ses, server,
            from_reconnect);
  if (rc) {
   mutex_unlock(&ses->session_mutex);
   goto out;
  }
 }

 rc = cifs_setup_session(0, ses, server, ses->local_nls);
 if ((rc == -EACCES) || (rc == -EKEYEXPIRED) || (rc == -EKEYREVOKED)) {
  /*
 * Try alternate password for next reconnect (key rotation
 * could be enabled on the server e.g.) if an alternate
 * password is available and the current password is expired,
 * but do not swap on non pwd related errors like host down
 */

  if (ses->password2)
   swap(ses->password2, ses->password);
 }
 if (rc) {
  mutex_unlock(&ses->session_mutex);
  if (rc == -EACCES && !tcon->retry)
   return -EHOSTDOWN;
  goto out;
 }

skip_sess_setup:
 if (!tcon->need_reconnect) {
  mutex_unlock(&ses->session_mutex);
  goto out;
 }
 cifs_mark_open_files_invalid(tcon);
 if (tcon->use_persistent)
  tcon->need_reopen_files = true;

 rc = cifs_tree_connect(0, tcon);

 cifs_dbg(FYI, "reconnect tcon rc = %d\n", rc);
 if (rc) {
  /* If sess reconnected but tcon didn't, something strange ... */
  mutex_unlock(&ses->session_mutex);
  cifs_dbg(VFS, "reconnect tcon failed rc = %d\n", rc);
  goto out;
 }

 spin_lock(&ses->ses_lock);
 if (ses->flags & CIFS_SES_FLAG_SCALE_CHANNELS) {
  spin_unlock(&ses->ses_lock);
  mutex_unlock(&ses->session_mutex);
  goto skip_add_channels;
 }
 ses->flags |= CIFS_SES_FLAG_SCALE_CHANNELS;
 spin_unlock(&ses->ses_lock);

 if (!rc &&
     (server->capabilities & SMB2_GLOBAL_CAP_MULTI_CHANNEL) &&
     server->ops->query_server_interfaces) {
  /*
 * query server network interfaces, in case they change.
 * Also mark the session as pending this update while the query
 * is in progress. This will be used to avoid calling
 * smb2_reconnect recursively.
 */

  ses->flags |= CIFS_SES_FLAGS_PENDING_QUERY_INTERFACES;
  xid = get_xid();
  rc = server->ops->query_server_interfaces(xid, tcon, false);
  free_xid(xid);
  ses->flags &= ~CIFS_SES_FLAGS_PENDING_QUERY_INTERFACES;

  if (!tcon->ipc && !tcon->dummy)
   queue_delayed_work(cifsiod_wq, &tcon->query_interfaces,
        (SMB_INTERFACE_POLL_INTERVAL * HZ));

  mutex_unlock(&ses->session_mutex);

  if (rc == -EOPNOTSUPP && ses->chan_count > 1) {
   /*
 * some servers like Azure SMB server do not advertise
 * that multichannel has been disabled with server
 * capabilities, rather return STATUS_NOT_IMPLEMENTED.
 * treat this as server not supporting multichannel
 */


   rc = cifs_chan_skip_or_disable(ses, server,
             from_reconnect);
   goto skip_add_channels;
  } else if (rc)
   cifs_dbg(FYI, "%s: failed to query server interfaces: %d\n",
     __func__, rc);

  if (ses->chan_max > ses->chan_count &&
      ses->iface_count &&
      !SERVER_IS_CHAN(server)) {
   if (ses->chan_count == 1)
    cifs_server_dbg(VFS, "supports multichannel now\n");

   cifs_try_adding_channels(ses);
  }
 } else {
  mutex_unlock(&ses->session_mutex);
 }

skip_add_channels:
 spin_lock(&ses->ses_lock);
 ses->flags &= ~CIFS_SES_FLAG_SCALE_CHANNELS;
 spin_unlock(&ses->ses_lock);

 if (smb2_command != SMB2_INTERNAL_CMD)
  mod_delayed_work(cifsiod_wq, &server->reconnect, 0);

 atomic_inc(&tconInfoReconnectCount);
out:
 /*
 * Check if handle based operation so we know whether we can continue
 * or not without returning to caller to reset file handle.
 */

 /*
 * BB Is flush done by server on drop of tcp session? Should we special
 * case it and skip above?
 */

 switch (smb2_command) {
 case SMB2_FLUSH:
 case SMB2_READ:
 case SMB2_WRITE:
 case SMB2_LOCK:
 case SMB2_QUERY_DIRECTORY:
 case SMB2_CHANGE_NOTIFY:
 case SMB2_QUERY_INFO:
 case SMB2_SET_INFO:
 case SMB2_IOCTL:
  rc = -EAGAIN;
 }
 return rc;
}

static void
fill_small_buf(__le16 smb2_command, struct cifs_tcon *tcon,
        struct TCP_Server_Info *server,
        void *buf,
        unsigned int *total_len)
{
 struct smb2_pdu *spdu = buf;
 /* lookup word count ie StructureSize from table */
 __u16 parmsize = smb2_req_struct_sizes[le16_to_cpu(smb2_command)];

 /*
 * smaller than SMALL_BUFFER_SIZE but bigger than fixed area of
 * largest operations (Create)
 */

 memset(buf, 0, 256);

 smb2_hdr_assemble(&spdu->hdr, smb2_command, tcon, server);
 spdu->StructureSize2 = cpu_to_le16(parmsize);

 *total_len = parmsize + sizeof(struct smb2_hdr);
}

/*
 * Allocate and return pointer to an SMB request hdr, and set basic
 * SMB information in the SMB header. If the return code is zero, this
 * function must have filled in request_buf pointer.
 */

static int __smb2_plain_req_init(__le16 smb2_command, struct cifs_tcon *tcon,
     struct TCP_Server_Info *server,
     void **request_buf, unsigned int *total_len)
{
 /* BB eventually switch this to SMB2 specific small buf size */
 switch (smb2_command) {
 case SMB2_SET_INFO:
 case SMB2_QUERY_INFO:
  *request_buf = cifs_buf_get();
  break;
 default:
  *request_buf = cifs_small_buf_get();
  break;
 }
 if (*request_buf == NULL) {
  /* BB should we add a retry in here if not a writepage? */
  return -ENOMEM;
 }

 fill_small_buf(smb2_command, tcon, server,
         (struct smb2_hdr *)(*request_buf),
         total_len);

 if (tcon != NULL) {
  uint16_t com_code = le16_to_cpu(smb2_command);
  cifs_stats_inc(&tcon->stats.smb2_stats.smb2_com_sent[com_code]);
  cifs_stats_inc(&tcon->num_smbs_sent);
 }

 return 0;
}

static int smb2_plain_req_init(__le16 smb2_command, struct cifs_tcon *tcon,
          struct TCP_Server_Info *server,
          void **request_buf, unsigned int *total_len)
{
 int rc;

 rc = smb2_reconnect(smb2_command, tcon, server, false);
 if (rc)
  return rc;

 return __smb2_plain_req_init(smb2_command, tcon, server, request_buf,
         total_len);
}

static int smb2_ioctl_req_init(u32 opcode, struct cifs_tcon *tcon,
          struct TCP_Server_Info *server,
          void **request_buf, unsigned int *total_len)
{
 /*
 * Skip reconnect in one of the following cases:
 * 1. For FSCTL_VALIDATE_NEGOTIATE_INFO IOCTLs
 * 2. For FSCTL_QUERY_NETWORK_INTERFACE_INFO IOCTL when called from
 * smb2_reconnect (indicated by CIFS_SES_FLAG_SCALE_CHANNELS ses flag)
 */

 if (opcode == FSCTL_VALIDATE_NEGOTIATE_INFO ||
     (opcode == FSCTL_QUERY_NETWORK_INTERFACE_INFO &&
      (tcon->ses->flags & CIFS_SES_FLAGS_PENDING_QUERY_INTERFACES)))
  return __smb2_plain_req_init(SMB2_IOCTL, tcon, server,
          request_buf, total_len);

 return smb2_plain_req_init(SMB2_IOCTL, tcon, server,
       request_buf, total_len);
}

/* For explanation of negotiate contexts see MS-SMB2 section 2.2.3.1 */

static void
build_preauth_ctxt(struct smb2_preauth_neg_context *pneg_ctxt)
{
 pneg_ctxt->ContextType = SMB2_PREAUTH_INTEGRITY_CAPABILITIES;
 pneg_ctxt->DataLength = cpu_to_le16(38);
 pneg_ctxt->HashAlgorithmCount = cpu_to_le16(1);
 pneg_ctxt->SaltLength = cpu_to_le16(SMB311_SALT_SIZE);
 get_random_bytes(pneg_ctxt->Salt, SMB311_SALT_SIZE);
 pneg_ctxt->HashAlgorithms = SMB2_PREAUTH_INTEGRITY_SHA512;
}

static void
build_compression_ctxt(struct smb2_compression_capabilities_context *pneg_ctxt)
{
 pneg_ctxt->ContextType = SMB2_COMPRESSION_CAPABILITIES;
 pneg_ctxt->DataLength =
  cpu_to_le16(sizeof(struct smb2_compression_capabilities_context)
     - sizeof(struct smb2_neg_context));
 pneg_ctxt->CompressionAlgorithmCount = cpu_to_le16(3);
 pneg_ctxt->CompressionAlgorithms[0] = SMB3_COMPRESS_LZ77;
 pneg_ctxt->CompressionAlgorithms[1] = SMB3_COMPRESS_LZ77_HUFF;
 pneg_ctxt->CompressionAlgorithms[2] = SMB3_COMPRESS_LZNT1;
}

static unsigned int
build_signing_ctxt(struct smb2_signing_capabilities *pneg_ctxt)
{
 unsigned int ctxt_len = sizeof(struct smb2_signing_capabilities);
 unsigned short num_algs = 1; /* number of signing algorithms sent */

 pneg_ctxt->ContextType = SMB2_SIGNING_CAPABILITIES;
 /*
 * Context Data length must be rounded to multiple of 8 for some servers
 */

 pneg_ctxt->DataLength = cpu_to_le16(ALIGN(sizeof(struct smb2_signing_capabilities) -
         sizeof(struct smb2_neg_context) +
         (num_algs * sizeof(u16)), 8));
 pneg_ctxt->SigningAlgorithmCount = cpu_to_le16(num_algs);
 pneg_ctxt->SigningAlgorithms[0] = cpu_to_le16(SIGNING_ALG_AES_CMAC);

 ctxt_len += sizeof(__le16) * num_algs;
 ctxt_len = ALIGN(ctxt_len, 8);
 return ctxt_len;
 /* TBD add SIGNING_ALG_AES_GMAC and/or SIGNING_ALG_HMAC_SHA256 */
}

static void
build_encrypt_ctxt(struct smb2_encryption_neg_context *pneg_ctxt)
{
 pneg_ctxt->ContextType = SMB2_ENCRYPTION_CAPABILITIES;
 if (require_gcm_256) {
  pneg_ctxt->DataLength = cpu_to_le16(4); /* Cipher Count + 1 cipher */
  pneg_ctxt->CipherCount = cpu_to_le16(1);
  pneg_ctxt->Ciphers[0] = SMB2_ENCRYPTION_AES256_GCM;
 } else if (enable_gcm_256) {
  pneg_ctxt->DataLength = cpu_to_le16(8); /* Cipher Count + 3 ciphers */
  pneg_ctxt->CipherCount = cpu_to_le16(3);
  pneg_ctxt->Ciphers[0] = SMB2_ENCRYPTION_AES128_GCM;
  pneg_ctxt->Ciphers[1] = SMB2_ENCRYPTION_AES256_GCM;
  pneg_ctxt->Ciphers[2] = SMB2_ENCRYPTION_AES128_CCM;
 } else {
  pneg_ctxt->DataLength = cpu_to_le16(6); /* Cipher Count + 2 ciphers */
  pneg_ctxt->CipherCount = cpu_to_le16(2);
  pneg_ctxt->Ciphers[0] = SMB2_ENCRYPTION_AES128_GCM;
  pneg_ctxt->Ciphers[1] = SMB2_ENCRYPTION_AES128_CCM;
 }
}

static unsigned int
build_netname_ctxt(struct smb2_netname_neg_context *pneg_ctxt, char *hostname)
{
 struct nls_table *cp = load_nls_default();

 pneg_ctxt->ContextType = SMB2_NETNAME_NEGOTIATE_CONTEXT_ID;

 /* copy up to max of first 100 bytes of server name to NetName field */
 pneg_ctxt->DataLength = cpu_to_le16(2 * cifs_strtoUTF16(pneg_ctxt->NetName, hostname, 100, cp));
 /* context size is DataLength + minimal smb2_neg_context */
 return ALIGN(le16_to_cpu(pneg_ctxt->DataLength) + sizeof(struct smb2_neg_context), 8);
}

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 void
assemble_neg_contexts(struct smb2_negotiate_req *req,
        struct TCP_Server_Info *server, unsigned int *total_len)
{
 unsigned int ctxt_len, neg_context_count;
 struct TCP_Server_Info *pserver;
 char *pneg_ctxt;
 char *hostname;

 if (*total_len > 200) {
  /* In case length corrupted don't want to overrun smb buffer */
  cifs_server_dbg(VFS, "Bad frame length assembling neg contexts\n");
  return;
 }

 /*
 * round up total_len of fixed part of SMB3 negotiate request to 8
 * byte boundary before adding negotiate contexts
 */

 *total_len = ALIGN(*total_len, 8);

 pneg_ctxt = (*total_len) + (char *)req;
 req->NegotiateContextOffset = cpu_to_le32(*total_len);

 build_preauth_ctxt((struct smb2_preauth_neg_context *)pneg_ctxt);
 ctxt_len = ALIGN(sizeof(struct smb2_preauth_neg_context), 8);
 *total_len += ctxt_len;
 pneg_ctxt += ctxt_len;

 build_encrypt_ctxt((struct smb2_encryption_neg_context *)pneg_ctxt);
 ctxt_len = ALIGN(sizeof(struct smb2_encryption_neg_context), 8);
 *total_len += ctxt_len;
 pneg_ctxt += ctxt_len;

 /*
 * secondary channels don't have the hostname field populated
 * use the hostname field in the primary channel instead
 */

 pserver = SERVER_IS_CHAN(server) ? server->primary_server : server;
 cifs_server_lock(pserver);
 hostname = pserver->hostname;
 if (hostname && (hostname[0] != 0)) {
  ctxt_len = build_netname_ctxt((struct smb2_netname_neg_context *)pneg_ctxt,
           hostname);
  *total_len += ctxt_len;
  pneg_ctxt += ctxt_len;
  neg_context_count = 3;
 } else
  neg_context_count = 2;
 cifs_server_unlock(pserver);

 build_posix_ctxt((struct smb2_posix_neg_context *)pneg_ctxt);
 *total_len += sizeof(struct smb2_posix_neg_context);
 pneg_ctxt += sizeof(struct smb2_posix_neg_context);
 neg_context_count++;

 if (server->compression.requested) {
  build_compression_ctxt((struct smb2_compression_capabilities_context *)
    pneg_ctxt);
  ctxt_len = ALIGN(sizeof(struct smb2_compression_capabilities_context), 8);
  *total_len += ctxt_len;
  pneg_ctxt += ctxt_len;
  neg_context_count++;
 }

 if (enable_negotiate_signing) {
  ctxt_len = build_signing_ctxt((struct smb2_signing_capabilities *)
    pneg_ctxt);
  *total_len += ctxt_len;
  pneg_ctxt += ctxt_len;
  neg_context_count++;
 }

 /* check for and add transport_capabilities and signing capabilities */
 req->NegotiateContextCount = cpu_to_le16(neg_context_count);

}

/* If invalid preauth context warn but use what we requested, SHA-512 */
static void decode_preauth_context(struct smb2_preauth_neg_context *ctxt)
{
 unsigned int len = le16_to_cpu(ctxt->DataLength);

 /*
 * Caller checked that DataLength remains within SMB boundary. We still
 * need to confirm that one HashAlgorithms member is accounted for.
 */

 if (len < MIN_PREAUTH_CTXT_DATA_LEN) {
  pr_warn_once("server sent bad preauth context\n");
  return;
 } else if (len < MIN_PREAUTH_CTXT_DATA_LEN + le16_to_cpu(ctxt->SaltLength)) {
  pr_warn_once("server sent invalid SaltLength\n");
  return;
 }
 if (le16_to_cpu(ctxt->HashAlgorithmCount) != 1)
  pr_warn_once("Invalid SMB3 hash algorithm count\n");
 if (ctxt->HashAlgorithms != SMB2_PREAUTH_INTEGRITY_SHA512)
  pr_warn_once("unknown SMB3 hash algorithm\n");
}

static void decode_compress_ctx(struct TCP_Server_Info *server,
    struct smb2_compression_capabilities_context *ctxt)
{
 unsigned int len = le16_to_cpu(ctxt->DataLength);
 __le16 alg;

 server->compression.enabled = false;

 /*
 * Caller checked that DataLength remains within SMB boundary. We still
 * need to confirm that one CompressionAlgorithms member is accounted
 * for.
 */

 if (len < 10) {
  pr_warn_once("server sent bad compression cntxt\n");
  return;
 }

 if (le16_to_cpu(ctxt->CompressionAlgorithmCount) != 1) {
  pr_warn_once("invalid SMB3 compress algorithm count\n");
  return;
 }

 alg = ctxt->CompressionAlgorithms[0];

 /* 'NONE' (0) compressor type is never negotiated */
 if (alg == 0 || le16_to_cpu(alg) > 3) {
  pr_warn_once("invalid compression algorithm '%u'\n", alg);
  return;
 }

 server->compression.alg = alg;
 server->compression.enabled = true;
}

static int decode_encrypt_ctx(struct TCP_Server_Info *server,
         struct smb2_encryption_neg_context *ctxt)
{
 unsigned int len = le16_to_cpu(ctxt->DataLength);

 cifs_dbg(FYI, "decode SMB3.11 encryption neg context of len %d\n", len);
 /*
 * Caller checked that DataLength remains within SMB boundary. We still
 * need to confirm that one Cipher flexible array member is accounted
 * for.
 */

 if (len < MIN_ENCRYPT_CTXT_DATA_LEN) {
  pr_warn_once("server sent bad crypto ctxt len\n");
  return -EINVAL;
 }

 if (le16_to_cpu(ctxt->CipherCount) != 1) {
  pr_warn_once("Invalid SMB3.11 cipher count\n");
  return -EINVAL;
 }
 cifs_dbg(FYI, "SMB311 cipher type:%d\n", le16_to_cpu(ctxt->Ciphers[0]));
 if (require_gcm_256) {
  if (ctxt->Ciphers[0] != SMB2_ENCRYPTION_AES256_GCM) {
   cifs_dbg(VFS, "Server does not support requested encryption type (AES256 GCM)\n");
   return -EOPNOTSUPP;
  }
 } else if (ctxt->Ciphers[0] == 0) {
  /*
 * e.g. if server only supported AES256_CCM (very unlikely)
 * or server supported no encryption types or had all disabled.
 * Since GLOBAL_CAP_ENCRYPTION will be not set, in the case
 * in which mount requested encryption ("seal") checks later
 * on during tree connection will return proper rc, but if
 * seal not requested by client, since server is allowed to
 * return 0 to indicate no supported cipher, we can't fail here
 */

  server->cipher_type = 0;
  server->capabilities &= ~SMB2_GLOBAL_CAP_ENCRYPTION;
  pr_warn_once("Server does not support requested encryption types\n");
  return 0;
 } else if ((ctxt->Ciphers[0] != SMB2_ENCRYPTION_AES128_CCM) &&
     (ctxt->Ciphers[0] != SMB2_ENCRYPTION_AES128_GCM) &&
     (ctxt->Ciphers[0] != SMB2_ENCRYPTION_AES256_GCM)) {
  /* server returned a cipher we didn't ask for */
  pr_warn_once("Invalid SMB3.11 cipher returned\n");
  return -EINVAL;
 }
 server->cipher_type = ctxt->Ciphers[0];
 server->capabilities |= SMB2_GLOBAL_CAP_ENCRYPTION;
 return 0;
}

static void decode_signing_ctx(struct TCP_Server_Info *server,
          struct smb2_signing_capabilities *pctxt)
{
 unsigned int len = le16_to_cpu(pctxt->DataLength);

 /*
 * Caller checked that DataLength remains within SMB boundary. We still
 * need to confirm that one SigningAlgorithms flexible array member is
 * accounted for.
 */

 if ((len < 4) || (len > 16)) {
  pr_warn_once("server sent bad signing negcontext\n");
  return;
 }
 if (le16_to_cpu(pctxt->SigningAlgorithmCount) != 1) {
  pr_warn_once("Invalid signing algorithm count\n");
  return;
 }
 if (le16_to_cpu(pctxt->SigningAlgorithms[0]) > 2) {
  pr_warn_once("unknown signing algorithm\n");
  return;
 }

 server->signing_negotiated = true;
 server->signing_algorithm = le16_to_cpu(pctxt->SigningAlgorithms[0]);
 cifs_dbg(FYI, "signing algorithm %d chosen\n",
       server->signing_algorithm);
}


static int smb311_decode_neg_context(struct smb2_negotiate_rsp *rsp,
         struct TCP_Server_Info *server,
         unsigned int len_of_smb)
{
 struct smb2_neg_context *pctx;
 unsigned int offset = le32_to_cpu(rsp->NegotiateContextOffset);
 unsigned int ctxt_cnt = le16_to_cpu(rsp->NegotiateContextCount);
 unsigned int len_of_ctxts, i;
 int rc = 0;

 cifs_dbg(FYI, "decoding %d negotiate contexts\n", ctxt_cnt);
 if (len_of_smb <= offset) {
  cifs_server_dbg(VFS, "Invalid response: negotiate context offset\n");
  return -EINVAL;
 }

 len_of_ctxts = len_of_smb - offset;

 for (i = 0; i < ctxt_cnt; i++) {
  int clen;
  /* check that offset is not beyond end of SMB */
  if (len_of_ctxts < sizeof(struct smb2_neg_context))
   break;

  pctx = (struct smb2_neg_context *)(offset + (char *)rsp);
  clen = sizeof(struct smb2_neg_context)
   + le16_to_cpu(pctx->DataLength);
  /*
 * 2.2.4 SMB2 NEGOTIATE Response
 * Subsequent negotiate contexts MUST appear at the first 8-byte
 * aligned offset following the previous negotiate context.
 */

  if (i + 1 != ctxt_cnt)
   clen = ALIGN(clen, 8);
  if (clen > len_of_ctxts)
   break;

  if (pctx->ContextType == SMB2_PREAUTH_INTEGRITY_CAPABILITIES)
   decode_preauth_context(
    (struct smb2_preauth_neg_context *)pctx);
  else if (pctx->ContextType == SMB2_ENCRYPTION_CAPABILITIES)
   rc = decode_encrypt_ctx(server,
    (struct smb2_encryption_neg_context *)pctx);
  else if (pctx->ContextType == SMB2_COMPRESSION_CAPABILITIES)
   decode_compress_ctx(server,
    (struct smb2_compression_capabilities_context *)pctx);
  else if (pctx->ContextType == SMB2_POSIX_EXTENSIONS_AVAILABLE)
   server->posix_ext_supported = true;
  else if (pctx->ContextType == SMB2_SIGNING_CAPABILITIES)
   decode_signing_ctx(server,
    (struct smb2_signing_capabilities *)pctx);
  else
   cifs_server_dbg(VFS, "unknown negcontext of type %d ignored\n",
    le16_to_cpu(pctx->ContextType));
  if (rc)
   break;

  offset += clen;
  len_of_ctxts -= clen;
 }
 return rc;
}

static struct create_posix *
create_posix_buf(umode_t mode)
{
 struct create_posix *buf;

 buf = kzalloc(sizeof(struct create_posix),
   GFP_KERNEL);
 if (!buf)
  return NULL;

 buf->ccontext.DataOffset =
  cpu_to_le16(offsetof(struct create_posix, Mode));
 buf->ccontext.DataLength = cpu_to_le32(4);
 buf->ccontext.NameOffset =
  cpu_to_le16(offsetof(struct create_posix, Name));
 buf->ccontext.NameLength = cpu_to_le16(16);

 /* SMB2_CREATE_TAG_POSIX is "0x93AD25509CB411E7B42383DE968BCD7C" */
 buf->Name[0] = 0x93;
 buf->Name[1] = 0xAD;
 buf->Name[2] = 0x25;
 buf->Name[3] = 0x50;
 buf->Name[4] = 0x9C;
 buf->Name[5] = 0xB4;
 buf->Name[6] = 0x11;
 buf->Name[7] = 0xE7;
 buf->Name[8] = 0xB4;
 buf->Name[9] = 0x23;
 buf->Name[10] = 0x83;
 buf->Name[11] = 0xDE;
 buf->Name[12] = 0x96;
 buf->Name[13] = 0x8B;
 buf->Name[14] = 0xCD;
 buf->Name[15] = 0x7C;
 buf->Mode = cpu_to_le32(mode);
 cifs_dbg(FYI, "mode on posix create 0%o\n", mode);
 return buf;
}

static int
add_posix_context(struct kvec *iov, unsigned int *num_iovec, umode_t mode)
{
 unsigned int num = *num_iovec;

 iov[num].iov_base = create_posix_buf(mode);
 if (mode == ACL_NO_MODE)
  cifs_dbg(FYI, "%s: no mode\n", __func__);
 if (iov[num].iov_base == NULL)
  return -ENOMEM;
 iov[num].iov_len = sizeof(struct create_posix);
 *num_iovec = num + 1;
 return 0;
}


/*
 *
 * SMB2 Worker functions follow:
 *
 * The general structure of the worker functions is:
 * 1) Call smb2_init (assembles SMB2 header)
 * 2) Initialize SMB2 command specific fields in fixed length area of SMB
 * 3) Call smb_sendrcv2 (sends request on socket and waits for response)
 * 4) Decode SMB2 command specific fields in the fixed length area
 * 5) Decode variable length data area (if any for this SMB2 command type)
 * 6) Call free smb buffer
 * 7) return
 *
 */


int
SMB2_negotiate(const unsigned int xid,
        struct cifs_ses *ses,
        struct TCP_Server_Info *server)
{
 struct smb_rqst rqst;
 struct smb2_negotiate_req *req;
 struct smb2_negotiate_rsp *rsp;
 struct kvec iov[1];
 struct kvec rsp_iov;
 int rc;
 int resp_buftype;
 int blob_offset, blob_length;
 char *security_blob;
 int flags = CIFS_NEG_OP;
 unsigned int total_len;

 cifs_dbg(FYI, "Negotiate protocol\n");

 if (!server) {
  WARN(1, "%s: server is NULL!\n", __func__);
  return -EIO;
 }

 rc = smb2_plain_req_init(SMB2_NEGOTIATE, NULL, server,
     (void **) &req, &total_len);
 if (rc)
  return rc;

 req->hdr.SessionId = 0;

 memset(server->preauth_sha_hash, 0, SMB2_PREAUTH_HASH_SIZE);
 memset(ses->preauth_sha_hash, 0, SMB2_PREAUTH_HASH_SIZE);

 if (strcmp(server->vals->version_string,
     SMB3ANY_VERSION_STRING) == 0) {
  req->Dialects[0] = cpu_to_le16(SMB30_PROT_ID);
  req->Dialects[1] = cpu_to_le16(SMB302_PROT_ID);
  req->Dialects[2] = cpu_to_le16(SMB311_PROT_ID);
  req->DialectCount = cpu_to_le16(3);
  total_len += 6;
 } else if (strcmp(server->vals->version_string,
     SMBDEFAULT_VERSION_STRING) == 0) {
  req->Dialects[0] = cpu_to_le16(SMB21_PROT_ID);
  req->Dialects[1] = cpu_to_le16(SMB30_PROT_ID);
  req->Dialects[2] = cpu_to_le16(SMB302_PROT_ID);
  req->Dialects[3] = cpu_to_le16(SMB311_PROT_ID);
  req->DialectCount = cpu_to_le16(4);
  total_len += 8;
 } else {
  /* otherwise send specific dialect */
  req->Dialects[0] = cpu_to_le16(server->vals->protocol_id);
  req->DialectCount = cpu_to_le16(1);
  total_len += 2;
 }

 /* only one of SMB2 signing flags may be set in SMB2 request */
 if (ses->sign)
  req->SecurityMode = cpu_to_le16(SMB2_NEGOTIATE_SIGNING_REQUIRED);
 else if (global_secflags & CIFSSEC_MAY_SIGN)
  req->SecurityMode = cpu_to_le16(SMB2_NEGOTIATE_SIGNING_ENABLED);
 else
  req->SecurityMode = 0;

 req->Capabilities = cpu_to_le32(server->vals->req_capabilities);
 if (ses->chan_max > 1)
  req->Capabilities |= cpu_to_le32(SMB2_GLOBAL_CAP_MULTI_CHANNEL);

 /* ClientGUID must be zero for SMB2.02 dialect */
 if (server->vals->protocol_id == SMB20_PROT_ID)
  memset(req->ClientGUID, 0, SMB2_CLIENT_GUID_SIZE);
 else {
  memcpy(req->ClientGUID, server->client_guid,
   SMB2_CLIENT_GUID_SIZE);
  if ((server->vals->protocol_id == SMB311_PROT_ID) ||
      (strcmp(server->vals->version_string,
       SMB3ANY_VERSION_STRING) == 0) ||
      (strcmp(server->vals->version_string,
       SMBDEFAULT_VERSION_STRING) == 0))
   assemble_neg_contexts(req, server, &total_len);
 }
 iov[0].iov_base = (char *)req;
 iov[0].iov_len = total_len;

 memset(&rqst, 0, sizeof(struct smb_rqst));
 rqst.rq_iov = iov;
 rqst.rq_nvec = 1;

 rc = cifs_send_recv(xid, ses, server,
       &rqst, &resp_buftype, flags, &rsp_iov);
 cifs_small_buf_release(req);
 rsp = (struct smb2_negotiate_rsp *)rsp_iov.iov_base;
 /*
 * No tcon so can't do
 * cifs_stats_inc(&tcon->stats.smb2_stats.smb2_com_fail[SMB2...]);
 */

 if (rc == -EOPNOTSUPP) {
  cifs_server_dbg(VFS, "Dialect not supported by server. Consider specifying vers=1.0 or vers=2.0 on mount for accessing older servers\n");
  goto neg_exit;
 } else if (rc != 0)
  goto neg_exit;

 rc = -EIO;
 if (strcmp(server->vals->version_string,
     SMB3ANY_VERSION_STRING) == 0) {
  if (rsp->DialectRevision == cpu_to_le16(SMB20_PROT_ID)) {
   cifs_server_dbg(VFS,
    "SMB2 dialect returned but not requested\n");
   goto neg_exit;
  } else if (rsp->DialectRevision == cpu_to_le16(SMB21_PROT_ID)) {
   cifs_server_dbg(VFS,
    "SMB2.1 dialect returned but not requested\n");
   goto neg_exit;
  } else if (rsp->DialectRevision == cpu_to_le16(SMB311_PROT_ID)) {
   /* ops set to 3.0 by default for default so update */
   server->ops = &smb311_operations;
   server->vals = &smb311_values;
  }
 } else if (strcmp(server->vals->version_string,
     SMBDEFAULT_VERSION_STRING) == 0) {
  if (rsp->DialectRevision == cpu_to_le16(SMB20_PROT_ID)) {
   cifs_server_dbg(VFS,
    "SMB2 dialect returned but not requested\n");
   goto neg_exit;
  } else if (rsp->DialectRevision == cpu_to_le16(SMB21_PROT_ID)) {
   /* ops set to 3.0 by default for default so update */
   server->ops = &smb21_operations;
   server->vals = &smb21_values;
  } else if (rsp->DialectRevision == cpu_to_le16(SMB311_PROT_ID)) {
   server->ops = &smb311_operations;
   server->vals = &smb311_values;
  }
 } else if (le16_to_cpu(rsp->DialectRevision) !=
    server->vals->protocol_id) {
  /* if requested single dialect ensure returned dialect matched */
  cifs_server_dbg(VFS, "Invalid 0x%x dialect returned: not requested\n",
    le16_to_cpu(rsp->DialectRevision));
  goto neg_exit;
 }

 cifs_dbg(FYI, "mode 0x%x\n", rsp->SecurityMode);

 if (rsp->DialectRevision == cpu_to_le16(SMB20_PROT_ID))
  cifs_dbg(FYI, "negotiated smb2.0 dialect\n");
 else if (rsp->DialectRevision == cpu_to_le16(SMB21_PROT_ID))
  cifs_dbg(FYI, "negotiated smb2.1 dialect\n");
 else if (rsp->DialectRevision == cpu_to_le16(SMB30_PROT_ID))
  cifs_dbg(FYI, "negotiated smb3.0 dialect\n");
 else if (rsp->DialectRevision == cpu_to_le16(SMB302_PROT_ID))
  cifs_dbg(FYI, "negotiated smb3.02 dialect\n");
 else if (rsp->DialectRevision == cpu_to_le16(SMB311_PROT_ID))
  cifs_dbg(FYI, "negotiated smb3.1.1 dialect\n");
 else {
  cifs_server_dbg(VFS, "Invalid dialect returned by server 0x%x\n",
    le16_to_cpu(rsp->DialectRevision));
  goto neg_exit;
 }

 rc = 0;
 server->dialect = le16_to_cpu(rsp->DialectRevision);

 /*
 * Keep a copy of the hash after negprot. This hash will be
 * the starting hash value for all sessions made from this
 * server.
 */

 memcpy(server->preauth_sha_hash, ses->preauth_sha_hash,
        SMB2_PREAUTH_HASH_SIZE);

 /* SMB2 only has an extended negflavor */
 server->negflavor = CIFS_NEGFLAVOR_EXTENDED;
 /* set it to the maximum buffer size value we can send with 1 credit */
 server->maxBuf = min_t(unsigned int, le32_to_cpu(rsp->MaxTransactSize),
          SMB2_MAX_BUFFER_SIZE);
 server->max_read = le32_to_cpu(rsp->MaxReadSize);
 server->max_write = le32_to_cpu(rsp->MaxWriteSize);
 server->sec_mode = le16_to_cpu(rsp->SecurityMode);
 if ((server->sec_mode & SMB2_SEC_MODE_FLAGS_ALL) != server->sec_mode)
  cifs_dbg(FYI, "Server returned unexpected security mode 0x%x\n",
    server->sec_mode);
 server->capabilities = le32_to_cpu(rsp->Capabilities);
 /* Internal types */
 server->capabilities |= SMB2_NT_FIND | SMB2_LARGE_FILES;

 /*
 * SMB3.0 supports only 1 cipher and doesn't have a encryption neg context
 * Set the cipher type manually.
 */

 if ((server->dialect == SMB30_PROT_ID ||
      server->dialect == SMB302_PROT_ID) &&
     (server->capabilities & SMB2_GLOBAL_CAP_ENCRYPTION))
  server->cipher_type = SMB2_ENCRYPTION_AES128_CCM;

 security_blob = smb2_get_data_area_len(&blob_offset, &blob_length,
            (struct smb2_hdr *)rsp);
 /*
 * See MS-SMB2 section 2.2.4: if no blob, client picks default which
 * for us will be
 * ses->sectype = RawNTLMSSP;
 * but for time being this is our only auth choice so doesn't matter.
 * We just found a server which sets blob length to zero expecting raw.
 */

 if (blob_length == 0) {
  cifs_dbg(FYI, "missing security blob on negprot\n");
  server->sec_ntlmssp = true;
 }

 rc = cifs_enable_signing(server, ses->sign);
 if (rc)
  goto neg_exit;
 if (blob_length) {
  rc = decode_negTokenInit(security_blob, blob_length, server);
  if (rc == 1)
   rc = 0;
  else if (rc == 0)
   rc = -EIO;
 }

 if (rsp->DialectRevision == cpu_to_le16(SMB311_PROT_ID)) {
  if (rsp->NegotiateContextCount)
   rc = smb311_decode_neg_context(rsp, server,
             rsp_iov.iov_len);
  else
   cifs_server_dbg(VFS, "Missing expected negotiate contexts\n");
 }

 if (server->cipher_type && !rc)
  rc = smb3_crypto_aead_allocate(server);
neg_exit:
 free_rsp_buf(resp_buftype, rsp);
 return rc;
}

int smb3_validate_negotiate(const unsigned int xid, struct cifs_tcon *tcon)
{
 int rc;
 struct validate_negotiate_info_req *pneg_inbuf;
 struct validate_negotiate_info_rsp *pneg_rsp = NULL;
 u32 rsplen;
 u32 inbuflen; /* max of 4 dialects */
 struct TCP_Server_Info *server = tcon->ses->server;

 cifs_dbg(FYI, "validate negotiate\n");

 /* In SMB3.11 preauth integrity supersedes validate negotiate */
 if (server->dialect == SMB311_PROT_ID)
  return 0;

 /*
 * validation ioctl must be signed, so no point sending this if we
 * can not sign it (ie are not known user).  Even if signing is not
 * required (enabled but not negotiated), in those cases we selectively
 * sign just this, the first and only signed request on a connection.
 * Having validation of negotiate info  helps reduce attack vectors.
 */

 if (tcon->ses->session_flags & SMB2_SESSION_FLAG_IS_GUEST)
  return 0; /* validation requires signing */

 if (tcon->ses->user_name == NULL) {
  cifs_dbg(FYI, "Can't validate negotiate: null user mount\n");
  return 0; /* validation requires signing */
 }

 if (tcon->ses->session_flags & SMB2_SESSION_FLAG_IS_NULL)
  cifs_tcon_dbg(VFS, "Unexpected null user (anonymous) auth flag sent by server\n");

 pneg_inbuf = kmalloc(sizeof(*pneg_inbuf), GFP_NOFS);
 if (!pneg_inbuf)
  return -ENOMEM;

 pneg_inbuf->Capabilities =
   cpu_to_le32(server->vals->req_capabilities);
 if (tcon->ses->chan_max > 1)
  pneg_inbuf->Capabilities |= cpu_to_le32(SMB2_GLOBAL_CAP_MULTI_CHANNEL);

 memcpy(pneg_inbuf->Guid, server->client_guid,
     SMB2_CLIENT_GUID_SIZE);

 if (tcon->ses->sign)
  pneg_inbuf->SecurityMode =
   cpu_to_le16(SMB2_NEGOTIATE_SIGNING_REQUIRED);
 else if (global_secflags & CIFSSEC_MAY_SIGN)
  pneg_inbuf->SecurityMode =
   cpu_to_le16(SMB2_NEGOTIATE_SIGNING_ENABLED);
 else
  pneg_inbuf->SecurityMode = 0;


 if (strcmp(server->vals->version_string,
  SMB3ANY_VERSION_STRING) == 0) {
  pneg_inbuf->Dialects[0] = cpu_to_le16(SMB30_PROT_ID);
  pneg_inbuf->Dialects[1] = cpu_to_le16(SMB302_PROT_ID);
  pneg_inbuf->Dialects[2] = cpu_to_le16(SMB311_PROT_ID);
  pneg_inbuf->DialectCount = cpu_to_le16(3);
  /* SMB 2.1 not included so subtract one dialect from len */
  inbuflen = sizeof(*pneg_inbuf) -
    (sizeof(pneg_inbuf->Dialects[0]));
 } else if (strcmp(server->vals->version_string,
  SMBDEFAULT_VERSION_STRING) == 0) {
  pneg_inbuf->Dialects[0] = cpu_to_le16(SMB21_PROT_ID);
  pneg_inbuf->Dialects[1] = cpu_to_le16(SMB30_PROT_ID);
  pneg_inbuf->Dialects[2] = cpu_to_le16(SMB302_PROT_ID);
  pneg_inbuf->Dialects[3] = cpu_to_le16(SMB311_PROT_ID);
  pneg_inbuf->DialectCount = cpu_to_le16(4);
  /* structure is big enough for 4 dialects */
  inbuflen = sizeof(*pneg_inbuf);
 } else {
  /* otherwise specific dialect was requested */
  pneg_inbuf->Dialects[0] =
   cpu_to_le16(server->vals->protocol_id);
  pneg_inbuf->DialectCount = cpu_to_le16(1);
  /* structure is big enough for 4 dialects, sending only 1 */
  inbuflen = sizeof(*pneg_inbuf) -
    sizeof(pneg_inbuf->Dialects[0]) * 3;
 }

 rc = SMB2_ioctl(xid, tcon, NO_FILE_ID, NO_FILE_ID,
  FSCTL_VALIDATE_NEGOTIATE_INFO,
  (char *)pneg_inbuf, inbuflen, CIFSMaxBufSize,
  (char **)&pneg_rsp, &rsplen);
 if (rc == -EOPNOTSUPP) {
  /*
 * Old Windows versions or Netapp SMB server can return
 * not supported error. Client should accept it.
 */

  cifs_tcon_dbg(VFS, "Server does not support validate negotiate\n");
  rc = 0;
  goto out_free_inbuf;
 } else if (rc != 0) {
  cifs_tcon_dbg(VFS, "validate protocol negotiate failed: %d\n",
         rc);
  rc = -EIO;
  goto out_free_inbuf;
 }

 rc = -EIO;
 if (rsplen != sizeof(*pneg_rsp)) {
  cifs_tcon_dbg(VFS, "Invalid protocol negotiate response size: %d\n",
         rsplen);

  /* relax check since Mac returns max bufsize allowed on ioctl */
  if (rsplen > CIFSMaxBufSize || rsplen < sizeof(*pneg_rsp))
   goto out_free_rsp;
 }

 /* check validate negotiate info response matches what we got earlier */
 if (pneg_rsp->Dialect != cpu_to_le16(server->dialect))
  goto vneg_out;

 if (pneg_rsp->SecurityMode != cpu_to_le16(server->sec_mode))
  goto vneg_out;

 /* do not validate server guid because not saved at negprot time yet */

 if ((le32_to_cpu(pneg_rsp->Capabilities) | SMB2_NT_FIND |
       SMB2_LARGE_FILES) != server->capabilities)
  goto vneg_out;

 /* validate negotiate successful */
 rc = 0;
 cifs_dbg(FYI, "validate negotiate info successful\n");
 goto out_free_rsp;

vneg_out:
 cifs_tcon_dbg(VFS, "protocol revalidation - security settings mismatch\n");
out_free_rsp:
 kfree(pneg_rsp);
out_free_inbuf:
 kfree(pneg_inbuf);
 return rc;
}

enum securityEnum
smb2_select_sectype(struct TCP_Server_Info *server, enum securityEnum requested)
{
 switch (requested) {
 case Kerberos:
 case RawNTLMSSP:
  return requested;
 case NTLMv2:
  return RawNTLMSSP;
 case Unspecified:
  if (server->sec_ntlmssp &&
   (global_secflags & CIFSSEC_MAY_NTLMSSP))
   return RawNTLMSSP;
  if ((server->sec_kerberos || server->sec_mskerberos || server->sec_iakerb) &&
   (global_secflags & CIFSSEC_MAY_KRB5))
   return Kerberos;
  fallthrough;
 default:
  return Unspecified;
 }
}

struct SMB2_sess_data {
 unsigned int xid;
 struct cifs_ses *ses;
 struct TCP_Server_Info *server;
 struct nls_table *nls_cp;
 void (*func)(struct SMB2_sess_data *);
 int result;
 u64 previous_session;

 /* we will send the SMB in three pieces:
 * a fixed length beginning part, an optional
 * SPNEGO blob (which can be zero length), and a
 * last part which will include the strings
 * and rest of bcc area. This allows us to avoid
 * a large buffer 17K allocation
 */

 int buf0_type;
 struct kvec iov[2];
};

static int
SMB2_sess_alloc_buffer(struct SMB2_sess_data *sess_data)
{
 int rc;
 struct cifs_ses *ses = sess_data->ses;
 struct TCP_Server_Info *server = sess_data->server;
 struct smb2_sess_setup_req *req;
 unsigned int total_len;
 bool is_binding = false;

 rc = smb2_plain_req_init(SMB2_SESSION_SETUP, NULL, server,
     (void **) &req,
     &total_len);
 if (rc)
  return rc;

 spin_lock(&ses->ses_lock);
 is_binding = (ses->ses_status == SES_GOOD);
 spin_unlock(&ses->ses_lock);

 if (is_binding) {
  req->hdr.SessionId = cpu_to_le64(ses->Suid);
  req->hdr.Flags |= SMB2_FLAGS_SIGNED;
  req->PreviousSessionId = 0;
  req->Flags = SMB2_SESSION_REQ_FLAG_BINDING;
  cifs_dbg(FYI, "Binding to sess id: %llx\n", ses->Suid);
 } else {
  /* First session, not a reauthenticate */
  req->hdr.SessionId = 0;
  /*
 * if reconnect, we need to send previous sess id
 * otherwise it is 0
 */

  req->PreviousSessionId = cpu_to_le64(sess_data->previous_session);
  req->Flags = 0; /* MBZ */
  cifs_dbg(FYI, "Fresh session. Previous: %llx\n",
    sess_data->previous_session);
 }

 /* enough to enable echos and oplocks and one max size write */
 if (server->credits >= server->max_credits)
  req->hdr.CreditRequest = cpu_to_le16(0);
 else
  req->hdr.CreditRequest = cpu_to_le16(
   min_t(int, server->max_credits -
         server->credits, 130));

 /* only one of SMB2 signing flags may be set in SMB2 request */
 if (server->sign)
  req->SecurityMode = SMB2_NEGOTIATE_SIGNING_REQUIRED;
 else if (global_secflags & CIFSSEC_MAY_SIGN) /* one flag unlike MUST_ */
  req->SecurityMode = SMB2_NEGOTIATE_SIGNING_ENABLED;
 else
  req->SecurityMode = 0;

#ifdef CONFIG_CIFS_DFS_UPCALL
 req->Capabilities = cpu_to_le32(SMB2_GLOBAL_CAP_DFS);
#else
 req->Capabilities = 0;
#endif /* DFS_UPCALL */

 req->Channel = 0; /* MBZ */

 sess_data->iov[0].iov_base = (char *)req;
 /* 1 for pad */
 sess_data->iov[0].iov_len = total_len - 1;
 /*
 * This variable will be used to clear the buffer
 * allocated above in case of any error in the calling function.
 */

 sess_data->buf0_type = CIFS_SMALL_BUFFER;

 return 0;
}

static void
SMB2_sess_free_buffer(struct SMB2_sess_data *sess_data)
{
 struct kvec *iov = sess_data->iov;

 /* iov[1] is already freed by caller */
 if (sess_data->buf0_type != CIFS_NO_BUFFER && iov[0].iov_base)
  memzero_explicit(iov[0].iov_base, iov[0].iov_len);

 free_rsp_buf(sess_data->buf0_type, iov[0].iov_base);
 sess_data->buf0_type = CIFS_NO_BUFFER;
}

static int
SMB2_sess_sendreceive(struct SMB2_sess_data *sess_data)
{
 int rc;
 struct smb_rqst rqst;
 struct smb2_sess_setup_req *req = sess_data->iov[0].iov_base;
 struct kvec rsp_iov = { NULL, 0 };

 /* Testing shows that buffer offset must be at location of Buffer[0] */
 req->SecurityBufferOffset =
  cpu_to_le16(sizeof(struct smb2_sess_setup_req));
 req->SecurityBufferLength = cpu_to_le16(sess_data->iov[1].iov_len);

 memset(&rqst, 0, sizeof(struct smb_rqst));
 rqst.rq_iov = sess_data->iov;
 rqst.rq_nvec = 2;

 /* BB add code to build os and lm fields */
 rc = cifs_send_recv(sess_data->xid, sess_data->ses,
       sess_data->server,
       &rqst,
       &sess_data->buf0_type,
       CIFS_LOG_ERROR | CIFS_SESS_OP, &rsp_iov);
 cifs_small_buf_release(sess_data->iov[0].iov_base);
 if (rc == 0)
  sess_data->ses->expired_pwd = false;
 else if ((rc == -EACCES) || (rc == -EKEYEXPIRED) || (rc == -EKEYREVOKED)) {
  if (sess_data->ses->expired_pwd == false)
   trace_smb3_key_expired(sess_data->server->hostname,
            sess_data->ses->user_name,
            sess_data->server->conn_id,
            &sess_data->server->dstaddr, rc);
  sess_data->ses->expired_pwd = true;
 }

 memcpy(&sess_data->iov[0], &rsp_iov, sizeof(struct kvec));

 return rc;
}

static int
SMB2_sess_establish_session(struct SMB2_sess_data *sess_data)
{
 int rc = 0;
 struct cifs_ses *ses = sess_data->ses;
 struct TCP_Server_Info *server = sess_data->server;

 cifs_server_lock(server);
 if (server->ops->generate_signingkey) {
  rc = server->ops->generate_signingkey(ses, server);
  if (rc) {
   cifs_dbg(FYI,
    "SMB3 session key generation failed\n");
   cifs_server_unlock(server);
   return rc;
  }
 }
 if (!server->session_estab) {
  server->sequence_number = 0x2;
  server->session_estab = true;
 }
 cifs_server_unlock(server);

 cifs_dbg(FYI, "SMB2/3 session established successfully\n");
 return rc;
}

#ifdef CONFIG_CIFS_UPCALL
static void
SMB2_auth_kerberos(struct SMB2_sess_data *sess_data)
{
 int rc;
 struct cifs_ses *ses = sess_data->ses;
 struct TCP_Server_Info *server = sess_data->server;
 struct cifs_spnego_msg *msg;
 struct key *spnego_key = NULL;
 struct smb2_sess_setup_rsp *rsp = NULL;
 bool is_binding = false;

 rc = SMB2_sess_alloc_buffer(sess_data);
 if (rc)
  goto out;

 spnego_key = cifs_get_spnego_key(ses, server);
 if (IS_ERR(spnego_key)) {
  rc = PTR_ERR(spnego_key);
  if (rc == -ENOKEY)
   cifs_dbg(VFS, "Verify user has a krb5 ticket and keyutils is installed\n");
  spnego_key = NULL;
  goto out;
 }

 msg = spnego_key->payload.data[0];
 /*
 * check version field to make sure that cifs.upcall is
 * sending us a response in an expected form
 */

 if (msg->version != CIFS_SPNEGO_UPCALL_VERSION) {
  cifs_dbg(VFS, "bad cifs.upcall version. Expected %d got %d\n",
    CIFS_SPNEGO_UPCALL_VERSION, msg->version);
  rc = -EKEYREJECTED;
  goto out_put_spnego_key;
 }

 spin_lock(&ses->ses_lock);
 is_binding = (ses->ses_status == SES_GOOD);
 spin_unlock(&ses->ses_lock);

 /* keep session key if binding */
 if (!is_binding) {
  kfree_sensitive(ses->auth_key.response);
  ses->auth_key.response = kmemdup(msg->data, msg->sesskey_len,
       GFP_KERNEL);
  if (!ses->auth_key.response) {
   cifs_dbg(VFS, "Kerberos can't allocate (%u bytes) memory\n",
     msg->sesskey_len);
   rc = -ENOMEM;
   goto out_put_spnego_key;
  }
  ses->auth_key.len = msg->sesskey_len;
 }

 sess_data->iov[1].iov_base = msg->data + msg->sesskey_len;
 sess_data->iov[1].iov_len = msg->secblob_len;

 rc = SMB2_sess_sendreceive(sess_data);
 if (rc)
  goto out_put_spnego_key;

 rsp = (struct smb2_sess_setup_rsp *)sess_data->iov[0].iov_base;
 /* keep session id and flags if binding */
 if (!is_binding) {
  ses->Suid = le64_to_cpu(rsp->hdr.SessionId);
  ses->session_flags = le16_to_cpu(rsp->SessionFlags);
 }

 rc = SMB2_sess_establish_session(sess_data);
out_put_spnego_key:
 key_invalidate(spnego_key);
 key_put(spnego_key);
 if (rc) {
  kfree_sensitive(ses->auth_key.response);
  ses->auth_key.response = NULL;
  ses->auth_key.len = 0;
 }
out:
 sess_data->result = rc;
 sess_data->func = NULL;
 SMB2_sess_free_buffer(sess_data);
}
#else
static void
SMB2_auth_kerberos(struct SMB2_sess_data *sess_data)
{
 cifs_dbg(VFS, "Kerberos negotiated but upcall support disabled!\n");
 sess_data->result = -EOPNOTSUPP;
 sess_data->func = NULL;
}
#endif

static void
SMB2_sess_auth_rawntlmssp_authenticate(struct SMB2_sess_data *sess_data);

static void
SMB2_sess_auth_rawntlmssp_negotiate(struct SMB2_sess_data *sess_data)
{
 int rc;
 struct cifs_ses *ses = sess_data->ses;
 struct TCP_Server_Info *server = sess_data->server;
 struct smb2_sess_setup_rsp *rsp = NULL;
 unsigned char *ntlmssp_blob = NULL;
 bool use_spnego = false/* else use raw ntlmssp */
 u16 blob_length = 0;
 bool is_binding = false;

 /*
 * If memory allocation is successful, caller of this function
 * frees it.
 */

 ses->ntlmssp = kmalloc(sizeof(struct ntlmssp_auth), GFP_KERNEL);
 if (!ses->ntlmssp) {
  rc = -ENOMEM;
  goto out_err;
 }
 ses->ntlmssp->sesskey_per_smbsess = true;

 rc = SMB2_sess_alloc_buffer(sess_data);
 if (rc)
  goto out_err;

 rc = build_ntlmssp_smb3_negotiate_blob(&ntlmssp_blob,
       &blob_length, ses, server,
       sess_data->nls_cp);
 if (rc)
  goto out;

 if (use_spnego) {
  /* BB eventually need to add this */
  cifs_dbg(VFS, "spnego not supported for SMB2 yet\n");
  rc = -EOPNOTSUPP;
  goto out;
 }
 sess_data->iov[1].iov_base = ntlmssp_blob;
 sess_data->iov[1].iov_len = blob_length;

 rc = SMB2_sess_sendreceive(sess_data);
 rsp = (struct smb2_sess_setup_rsp *)sess_data->iov[0].iov_base;

 /* If true, rc here is expected and not an error */
 if (sess_data->buf0_type != CIFS_NO_BUFFER &&
  rsp->hdr.Status == STATUS_MORE_PROCESSING_REQUIRED)
  rc = 0;

 if (rc)
  goto out;

 if (offsetof(struct smb2_sess_setup_rsp, Buffer) !=
   le16_to_cpu(rsp->SecurityBufferOffset)) {
  cifs_dbg(VFS, "Invalid security buffer offset %d\n",
   le16_to_cpu(rsp->SecurityBufferOffset));
  rc = -EIO;
  goto out;
 }
 rc = decode_ntlmssp_challenge(rsp->Buffer,
   le16_to_cpu(rsp->SecurityBufferLength), ses);
 if (rc)
  goto out;

 cifs_dbg(FYI, "rawntlmssp session setup challenge phase\n");

 spin_lock(&ses->ses_lock);
 is_binding = (ses->ses_status == SES_GOOD);
 spin_unlock(&ses->ses_lock);

 /* keep existing ses id and flags if binding */
 if (!is_binding) {
  ses->Suid = le64_to_cpu(rsp->hdr.SessionId);
  ses->session_flags = le16_to_cpu(rsp->SessionFlags);
 }

out:
 kfree_sensitive(ntlmssp_blob);
 SMB2_sess_free_buffer(sess_data);
 if (!rc) {
  sess_data->result = 0;
  sess_data->func = SMB2_sess_auth_rawntlmssp_authenticate;
  return;
 }
out_err:
 kfree_sensitive(ses->ntlmssp);
 ses->ntlmssp = NULL;
 sess_data->result = rc;
 sess_data->func = NULL;
}

static void
SMB2_sess_auth_rawntlmssp_authenticate(struct SMB2_sess_data *sess_data)
{
 int rc;
 struct cifs_ses *ses = sess_data->ses;
 struct TCP_Server_Info *server = sess_data->server;
 struct smb2_sess_setup_req *req;
 struct smb2_sess_setup_rsp *rsp = NULL;
 unsigned char *ntlmssp_blob = NULL;
 bool use_spnego = false/* else use raw ntlmssp */
 u16 blob_length = 0;
 bool is_binding = false;

 rc = SMB2_sess_alloc_buffer(sess_data);
 if (rc)
  goto out;

 req = (struct smb2_sess_setup_req *) sess_data->iov[0].iov_base;
 req->hdr.SessionId = cpu_to_le64(ses->Suid);

 rc = build_ntlmssp_auth_blob(&ntlmssp_blob, &blob_length,
         ses, server,
         sess_data->nls_cp);
 if (rc) {
  cifs_dbg(FYI, "build_ntlmssp_auth_blob failed %d\n", rc);
  goto out;
 }

 if (use_spnego) {
  /* BB eventually need to add this */
  cifs_dbg(VFS, "spnego not supported for SMB2 yet\n");
  rc = -EOPNOTSUPP;
  goto out;
 }
 sess_data->iov[1].iov_base = ntlmssp_blob;
 sess_data->iov[1].iov_len = blob_length;

 rc = SMB2_sess_sendreceive(sess_data);
 if (rc)
  goto out;

 rsp = (struct smb2_sess_setup_rsp *)sess_data->iov[0].iov_base;

 spin_lock(&ses->ses_lock);
 is_binding = (ses->ses_status == SES_GOOD);
 spin_unlock(&ses->ses_lock);

 /* keep existing ses id and flags if binding */
 if (!is_binding) {
  ses->Suid = le64_to_cpu(rsp->hdr.SessionId);
  ses->session_flags = le16_to_cpu(rsp->SessionFlags);
 }

 rc = SMB2_sess_establish_session(sess_data);
#ifdef CONFIG_CIFS_DEBUG_DUMP_KEYS
 if (ses->server->dialect < SMB30_PROT_ID) {
  cifs_dbg(VFS, "%s: dumping generated SMB2 session keys\n", __func__);
  /*
 * The session id is opaque in terms of endianness, so we can't
 * print it as a long long. we dump it as we got it on the wire
 */

  cifs_dbg(VFS, "Session Id %*ph\n", (int)sizeof(ses->Suid),
    &ses->Suid);
  cifs_dbg(VFS, "Session Key %*ph\n",
    SMB2_NTLMV2_SESSKEY_SIZE, ses->auth_key.response);
  cifs_dbg(VFS, "Signing Key %*ph\n",
    SMB3_SIGN_KEY_SIZE, ses->auth_key.response);
 }
#endif
out:
 kfree_sensitive(ntlmssp_blob);
 SMB2_sess_free_buffer(sess_data);
 kfree_sensitive(ses->ntlmssp);
 ses->ntlmssp = NULL;
 sess_data->result = rc;
 sess_data->func = NULL;
}

static int
SMB2_select_sec(struct SMB2_sess_data *sess_data)
{
 int type;
 struct cifs_ses *ses = sess_data->ses;
 struct TCP_Server_Info *server = sess_data->server;

 type = smb2_select_sectype(server, ses->sectype);
 cifs_dbg(FYI, "sess setup type %d\n", type);
 if (type == Unspecified) {
  cifs_dbg(VFS, "Unable to select appropriate authentication method!\n");
  return -EINVAL;
 }

 switch (type) {
 case Kerberos:
  sess_data->func = SMB2_auth_kerberos;
  break;
 case RawNTLMSSP:
  sess_data->func = SMB2_sess_auth_rawntlmssp_negotiate;
  break;
 default:
  cifs_dbg(VFS, "secType %d not supported!\n", type);
  return -EOPNOTSUPP;
 }

 return 0;
}

int
SMB2_sess_setup(const unsigned int xid, struct cifs_ses *ses,
  struct TCP_Server_Info *server,
  const struct nls_table *nls_cp)
{
 int rc = 0;
 struct SMB2_sess_data *sess_data;

 cifs_dbg(FYI, "Session Setup\n");

 if (!server) {
  WARN(1, "%s: server is NULL!\n", __func__);
  return -EIO;
 }

 sess_data = kzalloc(sizeof(struct SMB2_sess_data), GFP_KERNEL);
 if (!sess_data)
  return -ENOMEM;

 sess_data->xid = xid;
 sess_data->ses = ses;
 sess_data->server = server;
 sess_data->buf0_type = CIFS_NO_BUFFER;
 sess_data->nls_cp = (struct nls_table *) nls_cp;
 sess_data->previous_session = ses->Suid;

 rc = SMB2_select_sec(sess_data);
 if (rc)
  goto out;

 /*
 * Initialize the session hash with the server one.
 */

 memcpy(ses->preauth_sha_hash, server->preauth_sha_hash,
        SMB2_PREAUTH_HASH_SIZE);

 while (sess_data->func)
  sess_data->func(sess_data);

 if ((ses->session_flags & SMB2_SESSION_FLAG_IS_GUEST) && (ses->sign))
  cifs_server_dbg(VFS, "signing requested but authenticated as guest\n");
 rc = sess_data->result;
out:
 kfree_sensitive(sess_data);
 return rc;
}

int
SMB2_logoff(const unsigned int xid, struct cifs_ses *ses)
{
 struct smb_rqst rqst;
 struct smb2_logoff_req *req; /* response is also trivial struct */
 int rc = 0;
 struct TCP_Server_Info *server;
 int flags = 0;
 unsigned int total_len;
 struct kvec iov[1];
 struct kvec rsp_iov;
 int resp_buf_type;

 cifs_dbg(FYI, "disconnect session %p\n", ses);

 if (ses && (ses->server))
  server = ses->server;
 else
  return -EIO;

 /* no need to send SMB logoff if uid already closed due to reconnect */
 spin_lock(&ses->chan_lock);
 if (CIFS_ALL_CHANS_NEED_RECONNECT(ses)) {
  spin_unlock(&ses->chan_lock);
  goto smb2_session_already_dead;
 }
 spin_unlock(&ses->chan_lock);

 rc = smb2_plain_req_init(SMB2_LOGOFF, NULL, ses->server,
     (void **) &req, &total_len);
 if (rc)
  return rc;

  /* since no tcon, smb2_init can not do this, so do here */
 req->hdr.SessionId = cpu_to_le64(ses->Suid);

 if (ses->session_flags & SMB2_SESSION_FLAG_ENCRYPT_DATA)
  flags |= CIFS_TRANSFORM_REQ;
 else if (server->sign)
  req->hdr.Flags |= SMB2_FLAGS_SIGNED;

 flags |= CIFS_NO_RSP_BUF;

 iov[0].iov_base = (char *)req;
 iov[0].iov_len = total_len;

 memset(&rqst, 0, sizeof(struct smb_rqst));
 rqst.rq_iov = iov;
 rqst.rq_nvec = 1;

 rc = cifs_send_recv(xid, ses, ses->server,
       &rqst, &resp_buf_type, flags, &rsp_iov);
 cifs_small_buf_release(req);
 /*
 * No tcon so can't do
 * cifs_stats_inc(&tcon->stats.smb2_stats.smb2_com_fail[SMB2...]);
 */


smb2_session_already_dead:
 return rc;
}

static inline void cifs_stats_fail_inc(struct cifs_tcon *tcon, uint16_t code)
{
 cifs_stats_inc(&tcon->stats.smb2_stats.smb2_com_failed[code]);
}

#define MAX_SHARENAME_LENGTH (255 /* server */ + 80 /* share */ + 1 /* NULL */)

/* These are similar values to what Windows uses */
static inline void init_copy_chunk_defaults(struct cifs_tcon *tcon)
{
 tcon->max_chunks = 256;
 tcon->max_bytes_chunk = 1048576;
 tcon->max_bytes_copy = 16777216;
}

int
SMB2_tcon(const unsigned int xid, struct cifs_ses *ses, const char *tree,
   struct cifs_tcon *tcon, const struct nls_table *cp)
{
 struct smb_rqst rqst;
 struct smb2_tree_connect_req *req;
 struct smb2_tree_connect_rsp *rsp = NULL;
 struct kvec iov[2];
 struct kvec rsp_iov = { NULL, 0 };
 int rc = 0;
 int resp_buftype;
 int unc_path_len;
 __le16 *unc_path = NULL;
 int flags = 0;
 unsigned int total_len;
 struct TCP_Server_Info *server = cifs_pick_channel(ses);

 cifs_dbg(FYI, "TCON\n");

 if (!server || !tree)
  return -EIO;

 unc_path = kmalloc(MAX_SHARENAME_LENGTH * 2, GFP_KERNEL);
 if (unc_path == NULL)
  return -ENOMEM;

 unc_path_len = cifs_strtoUTF16(unc_path, tree, strlen(tree), cp);
 if (unc_path_len <= 0) {
  kfree(unc_path);
  return -EINVAL;
 }
 unc_path_len *= 2;

 /* SMB2 TREE_CONNECT request must be called with TreeId == 0 */
 tcon->tid = 0;
 atomic_set(&tcon->num_remote_opens, 0);
 rc = smb2_plain_req_init(SMB2_TREE_CONNECT, tcon, server,
     (void **) &req, &total_len);
 if (rc) {
  kfree(unc_path);
  return rc;
 }

 if (smb3_encryption_required(tcon))
  flags |= CIFS_TRANSFORM_REQ;

 iov[0].iov_base = (char *)req;
 /* 1 for pad */
 iov[0].iov_len = total_len - 1;

 /* Testing shows that buffer offset must be at location of Buffer[0] */
 req->PathOffset = cpu_to_le16(sizeof(struct smb2_tree_connect_req));
 req->PathLength = cpu_to_le16(unc_path_len);
 iov[1].iov_base = unc_path;
 iov[1].iov_len = unc_path_len;

 /*
 * 3.11 tcon req must be signed if not encrypted. See MS-SMB2 3.2.4.1.1
 * unless it is guest or anonymous user. See MS-SMB2 3.2.5.3.1
 * (Samba servers don't always set the flag so also check if null user)
 */

 if ((server->dialect == SMB311_PROT_ID) &&
     !smb3_encryption_required(tcon) &&
     !(ses->session_flags &
      (SMB2_SESSION_FLAG_IS_GUEST|SMB2_SESSION_FLAG_IS_NULL)) &&
     ((ses->user_name != NULL) || (ses->sectype == Kerberos)))
  req->hdr.Flags |= SMB2_FLAGS_SIGNED;

 memset(&rqst, 0, sizeof(struct smb_rqst));
 rqst.rq_iov = iov;
 rqst.rq_nvec = 2;

 /* Need 64 for max size write so ask for more in case not there yet */
 if (server->credits >= server->max_credits)
  req->hdr.CreditRequest = cpu_to_le16(0);
 else
  req->hdr.CreditRequest = cpu_to_le16(
   min_t(int, server->max_credits -
         server->credits, 64));

 rc = cifs_send_recv(xid, ses, server,
       &rqst, &resp_buftype, flags, &rsp_iov);
 cifs_small_buf_release(req);
 rsp = (struct smb2_tree_connect_rsp *)rsp_iov.iov_base;
 trace_smb3_tcon(xid, tcon->tid, ses->Suid, tree, rc);
 if ((rc != 0) || (rsp == NULL)) {
  cifs_stats_fail_inc(tcon, SMB2_TREE_CONNECT_HE);
  tcon->need_reconnect = true;
  goto tcon_error_exit;
 }

 switch (rsp->ShareType) {
 case SMB2_SHARE_TYPE_DISK:
  cifs_dbg(FYI, "connection to disk share\n");
  break;
 case SMB2_SHARE_TYPE_PIPE:
  tcon->pipe = true;
  cifs_dbg(FYI, "connection to pipe share\n");
  break;
 case SMB2_SHARE_TYPE_PRINT:
  tcon->print = true;
  cifs_dbg(FYI, "connection to printer\n");
  break;
 default:
  cifs_server_dbg(VFS, "unknown share type %d\n", rsp->ShareType);
  rc = -EOPNOTSUPP;
  goto tcon_error_exit;
 }

 tcon->share_flags = le32_to_cpu(rsp->ShareFlags);
 tcon->capabilities = rsp->Capabilities; /* we keep caps little endian */
 tcon->maximal_access = le32_to_cpu(rsp->MaximalAccess);
 tcon->tid = le32_to_cpu(rsp->hdr.Id.SyncId.TreeId);
 strscpy(tcon->tree_name, tree, sizeof(tcon->tree_name));

 if ((rsp->Capabilities & SMB2_SHARE_CAP_DFS) &&
     ((tcon->share_flags & SHI1005_FLAGS_DFS) == 0))
  cifs_tcon_dbg(VFS, "DFS capability contradicts DFS flag\n");

 if (tcon->seal &&
     !(server->capabilities & SMB2_GLOBAL_CAP_ENCRYPTION))
  cifs_tcon_dbg(VFS, "Encryption is requested but not supported\n");

 init_copy_chunk_defaults(tcon);
 if (server->ops->validate_negotiate)
  rc = server->ops->validate_negotiate(xid, tcon);
 if (rc == 0) /* See MS-SMB2 2.2.10 and 3.2.5.5 */
  if (tcon->share_flags & SMB2_SHAREFLAG_ISOLATED_TRANSPORT)
   server->nosharesock = true;
tcon_exit:

 free_rsp_buf(resp_buftype, rsp);
 kfree(unc_path);
 return rc;

tcon_error_exit:
 if (rsp && rsp->hdr.Status == STATUS_BAD_NETWORK_NAME)
  cifs_dbg(VFS | ONCE, "BAD_NETWORK_NAME: %s\n", tree);
 goto tcon_exit;
}

int
SMB2_tdis(const unsigned int xid, struct cifs_tcon *tcon)
{
 struct smb_rqst rqst;
 struct smb2_tree_disconnect_req *req; /* response is trivial */
 int rc = 0;
 struct cifs_ses *ses = tcon->ses;
 struct TCP_Server_Info *server = cifs_pick_channel(ses);
 int flags = 0;
 unsigned int total_len;
 struct kvec iov[1];
 struct kvec rsp_iov;
 int resp_buf_type;

 cifs_dbg(FYI, "Tree Disconnect\n");

 if (!ses || !(ses->server))
  return -EIO;

 trace_smb3_tdis_enter(xid, tcon->tid, ses->Suid, tcon->tree_name);
 spin_lock(&ses->chan_lock);
 if ((tcon->need_reconnect) ||
     (CIFS_ALL_CHANS_NEED_RECONNECT(tcon->ses))) {
  spin_unlock(&ses->chan_lock);
  return 0;
 }
 spin_unlock(&ses->chan_lock);

 invalidate_all_cached_dirs(tcon);

 rc = smb2_plain_req_init(SMB2_TREE_DISCONNECT, tcon, server,
     (void **) &req,
     &total_len);
 if (rc)
  return rc;

 if (smb3_encryption_required(tcon))
  flags |= CIFS_TRANSFORM_REQ;

 flags |= CIFS_NO_RSP_BUF;

 iov[0].iov_base = (char *)req;
 iov[0].iov_len = total_len;

 memset(&rqst, 0, sizeof(struct smb_rqst));
 rqst.rq_iov = iov;
 rqst.rq_nvec = 1;

 rc = cifs_send_recv(xid, ses, server,
       &rqst, &resp_buf_type, flags, &rsp_iov);
 cifs_small_buf_release(req);
 if (rc) {
  cifs_stats_fail_inc(tcon, SMB2_TREE_DISCONNECT_HE);
  trace_smb3_tdis_err(xid, tcon->tid, ses->Suid, rc);
 }
 trace_smb3_tdis_done(xid, tcon->tid, ses->Suid);

 return rc;
}


static struct create_durable *
create_durable_buf(void)
{
 struct create_durable *buf;

 buf = kzalloc(sizeof(struct create_durable), GFP_KERNEL);
 if (!buf)
  return NULL;

 buf->ccontext.DataOffset = cpu_to_le16(offsetof
--> --------------------

--> maximum size reached

--> --------------------

Messung V0.5
C=96 H=94 G=94

¤ Dauer der Verarbeitung: 0.20 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.






                                                                                                                                                                                                                                                                                                                                                                                                     


Neuigkeiten

     Aktuelles
     Motto des Tages

Software

     Produkte
     Quellcodebibliothek

Aktivitäten

     Artikel über Sicherheit
     Anleitung zur Aktivierung von SSL

Muße

     Gedichte
     Musik
     Bilder

Jenseits des Üblichen ....
    

Besucherstatistik

Besucherstatistik

Monitoring

Montastic status badge