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


Quelle  connect.c   Sprache: C

 
// SPDX-License-Identifier: LGPL-2.1
/*
 *
 *   Copyright (C) International Business Machines  Corp., 2002,2011
 *   Author(s): Steve French (sfrench@us.ibm.com)
 *
 */

#include <linux/fs.h>
#include <linux/net.h>
#include <linux/string.h>
#include <linux/sched/mm.h>
#include <linux/sched/signal.h>
#include <linux/list.h>
#include <linux/wait.h>
#include <linux/slab.h>
#include <linux/pagemap.h>
#include <linux/ctype.h>
#include <linux/utsname.h>
#include <linux/mempool.h>
#include <linux/delay.h>
#include <linux/completion.h>
#include <linux/kthread.h>
#include <linux/pagevec.h>
#include <linux/freezer.h>
#include <linux/namei.h>
#include <linux/uuid.h>
#include <linux/uaccess.h>
#include <asm/processor.h>
#include <linux/inet.h>
#include <linux/module.h>
#include <keys/user-type.h>
#include <net/ipv6.h>
#include <linux/parser.h>
#include <linux/bvec.h>
#include "cifspdu.h"
#include "cifsglob.h"
#include "cifsproto.h"
#include "cifs_unicode.h"
#include "cifs_debug.h"
#include "cifs_fs_sb.h"
#include "ntlmssp.h"
#include "nterr.h"
#include "rfc1002pdu.h"
#include "fscache.h"
#include "smb2proto.h"
#include "smbdirect.h"
#include "dns_resolve.h"
#ifdef CONFIG_CIFS_DFS_UPCALL
#include "dfs.h"
#include "dfs_cache.h"
#endif
#include "fs_context.h"
#include "cifs_swn.h"

/* FIXME: should these be tunable? */
#define TLINK_ERROR_EXPIRE (1 * HZ)
#define TLINK_IDLE_EXPIRE (600 * HZ)

/* Drop the connection to not overload the server */
#define MAX_STATUS_IO_TIMEOUT   5

static int ip_connect(struct TCP_Server_Info *server);
static int generic_ip_connect(struct TCP_Server_Info *server);
static void tlink_rb_insert(struct rb_root *root, struct tcon_link *new_tlink);
static void cifs_prune_tlinks(struct work_struct *work);

/*
 * Resolve hostname and set ip addr in tcp ses. Useful for hostnames that may
 * get their ip addresses changed at some point.
 *
 * This should be called with server->srv_mutex held.
 */

static int reconn_set_ipaddr_from_hostname(struct TCP_Server_Info *server)
{
 struct sockaddr_storage ss;
 int rc;

 if (!server->hostname)
  return -EINVAL;

 /* if server hostname isn't populated, there's nothing to do here */
 if (server->hostname[0] == '\0')
  return 0;

 spin_lock(&server->srv_lock);
 ss = server->dstaddr;
 spin_unlock(&server->srv_lock);

 rc = dns_resolve_name(server->dns_dom, server->hostname,
         strlen(server->hostname),
         (struct sockaddr *)&ss);
 if (!rc) {
  spin_lock(&server->srv_lock);
  memcpy(&server->dstaddr, &ss, sizeof(server->dstaddr));
  spin_unlock(&server->srv_lock);
 }
 return rc;
}

void smb2_query_server_interfaces(struct work_struct *work)
{
 int rc;
 int xid;
 struct cifs_tcon *tcon = container_of(work,
     struct cifs_tcon,
     query_interfaces.work);
 struct TCP_Server_Info *server = tcon->ses->server;

 /*
 * query server network interfaces, in case they change
 */

 if (!server->ops->query_server_interfaces)
  return;

 xid = get_xid();
 rc = server->ops->query_server_interfaces(xid, tcon, false);
 free_xid(xid);

 if (rc)
  cifs_dbg(FYI, "%s: failed to query server interfaces: %d\n",
    __func__, rc);

 queue_delayed_work(cifsiod_wq, &tcon->query_interfaces,
      (SMB_INTERFACE_POLL_INTERVAL * HZ));
}

#define set_need_reco(server) \
do { \
 spin_lock(&server->srv_lock); \
 if (server->tcpStatus != CifsExiting) \
  server->tcpStatus = CifsNeedReconnect; \
 spin_unlock(&server->srv_lock); \
while (0)

/*
 * Update the tcpStatus for the server.
 * This is used to signal the cifsd thread to call cifs_reconnect
 * ONLY cifsd thread should call cifs_reconnect. For any other
 * thread, use this function
 *
 * @server: the tcp ses for which reconnect is needed
 * @all_channels: if this needs to be done for all channels
 */

void
cifs_signal_cifsd_for_reconnect(struct TCP_Server_Info *server,
    bool all_channels)
{
 struct TCP_Server_Info *nserver;
 struct cifs_ses *ses;
 LIST_HEAD(reco);
 int i;

 /* if we need to signal just this channel */
 if (!all_channels) {
  set_need_reco(server);
  return;
 }

 if (SERVER_IS_CHAN(server))
  server = server->primary_server;
 scoped_guard(spinlock, &cifs_tcp_ses_lock) {
  set_need_reco(server);
  list_for_each_entry(ses, &server->smb_ses_list, smb_ses_list) {
   spin_lock(&ses->ses_lock);
   if (ses->ses_status == SES_EXITING) {
    spin_unlock(&ses->ses_lock);
    continue;
   }
   spin_lock(&ses->chan_lock);
   for (i = 1; i < ses->chan_count; i++) {
    nserver = ses->chans[i].server;
    if (!nserver)
     continue;
    nserver->srv_count++;
    list_add(&nserver->rlist, &reco);
   }
   spin_unlock(&ses->chan_lock);
   spin_unlock(&ses->ses_lock);
  }
 }

 list_for_each_entry_safe(server, nserver, &reco, rlist) {
  list_del_init(&server->rlist);
  set_need_reco(server);
  cifs_put_tcp_session(server, 0);
 }
}

/*
 * Mark all sessions and tcons for reconnect.
 * IMPORTANT: make sure that this gets called only from
 * cifsd thread. For any other thread, use
 * cifs_signal_cifsd_for_reconnect
 *
 * @server: the tcp ses for which reconnect is needed
 * @server needs to be previously set to CifsNeedReconnect.
 * @mark_smb_session: whether even sessions need to be marked
 */

void
cifs_mark_tcp_ses_conns_for_reconnect(struct TCP_Server_Info *server,
          bool mark_smb_session)
{
 struct TCP_Server_Info *pserver;
 struct cifs_ses *ses, *nses;
 struct cifs_tcon *tcon;

 /*
 * before reconnecting the tcp session, mark the smb session (uid) and the tid bad so they
 * are not used until reconnected.
 */

 cifs_dbg(FYI, "%s: marking necessary sessions and tcons for reconnect\n", __func__);

 /* If server is a channel, select the primary channel */
 pserver = SERVER_IS_CHAN(server) ? server->primary_server : server;

 /*
 * if the server has been marked for termination, there is a
 * chance that the remaining channels all need reconnect. To be
 * on the safer side, mark the session and trees for reconnect
 * for this scenario. This might cause a few redundant session
 * setup and tree connect requests, but it is better than not doing
 * a tree connect when needed, and all following requests failing
 */

 if (server->terminate) {
  mark_smb_session = true;
  server = pserver;
 }

 spin_lock(&cifs_tcp_ses_lock);
 list_for_each_entry_safe(ses, nses, &pserver->smb_ses_list, smb_ses_list) {
  spin_lock(&ses->ses_lock);
  if (ses->ses_status == SES_EXITING) {
   spin_unlock(&ses->ses_lock);
   continue;
  }
  spin_unlock(&ses->ses_lock);

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

  if (!cifs_chan_is_iface_active(ses, server)) {
   spin_unlock(&ses->chan_lock);
   cifs_chan_update_iface(ses, server);
   spin_lock(&ses->chan_lock);
  }

  if (!mark_smb_session && cifs_chan_needs_reconnect(ses, server)) {
   spin_unlock(&ses->chan_lock);
   continue;
  }

  if (mark_smb_session)
   CIFS_SET_ALL_CHANS_NEED_RECONNECT(ses);
  else
   cifs_chan_set_need_reconnect(ses, server);

  cifs_dbg(FYI, "%s: channel connect bitmap: 0x%lx\n",
    __func__, ses->chans_need_reconnect);

  /* If all channels need reconnect, then tcon needs reconnect */
  if (!mark_smb_session && !CIFS_ALL_CHANS_NEED_RECONNECT(ses)) {
   spin_unlock(&ses->chan_lock);
   continue;
  }
  spin_unlock(&ses->chan_lock);

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

  list_for_each_entry(tcon, &ses->tcon_list, tcon_list) {
   tcon->need_reconnect = true;
   spin_lock(&tcon->tc_lock);
   tcon->status = TID_NEED_RECON;
   spin_unlock(&tcon->tc_lock);

   cancel_delayed_work(&tcon->query_interfaces);
  }
  if (ses->tcon_ipc) {
   ses->tcon_ipc->need_reconnect = true;
   spin_lock(&ses->tcon_ipc->tc_lock);
   ses->tcon_ipc->status = TID_NEED_RECON;
   spin_unlock(&ses->tcon_ipc->tc_lock);
  }
 }
 spin_unlock(&cifs_tcp_ses_lock);
}

static void
cifs_abort_connection(struct TCP_Server_Info *server)
{
 struct mid_q_entry *mid, *nmid;
 struct list_head retry_list;

 server->maxBuf = 0;
 server->max_read = 0;

 /* do not want to be sending data on a socket we are freeing */
 cifs_dbg(FYI, "%s: tearing down socket\n", __func__);
 cifs_server_lock(server);
 if (server->ssocket) {
  cifs_dbg(FYI, "State: 0x%x Flags: 0x%lx\n", server->ssocket->state,
    server->ssocket->flags);
  kernel_sock_shutdown(server->ssocket, SHUT_WR);
  cifs_dbg(FYI, "Post shutdown state: 0x%x Flags: 0x%lx\n", server->ssocket->state,
    server->ssocket->flags);
  sock_release(server->ssocket);
  server->ssocket = NULL;
 }
 server->sequence_number = 0;
 server->session_estab = false;
 kfree_sensitive(server->session_key.response);
 server->session_key.response = NULL;
 server->session_key.len = 0;
 server->lstrp = jiffies;

 /* mark submitted MIDs for retry and issue callback */
 INIT_LIST_HEAD(&retry_list);
 cifs_dbg(FYI, "%s: moving mids to private list\n", __func__);
 spin_lock(&server->mid_queue_lock);
 list_for_each_entry_safe(mid, nmid, &server->pending_mid_q, qhead) {
  kref_get(&mid->refcount);
  if (mid->mid_state == MID_REQUEST_SUBMITTED)
   mid->mid_state = MID_RETRY_NEEDED;
  list_move(&mid->qhead, &retry_list);
  mid->deleted_from_q = true;
 }
 spin_unlock(&server->mid_queue_lock);
 cifs_server_unlock(server);

 cifs_dbg(FYI, "%s: issuing mid callbacks\n", __func__);
 list_for_each_entry_safe(mid, nmid, &retry_list, qhead) {
  list_del_init(&mid->qhead);
  mid_execute_callback(mid);
  release_mid(mid);
 }

 if (cifs_rdma_enabled(server)) {
  cifs_server_lock(server);
  smbd_destroy(server);
  cifs_server_unlock(server);
 }
}

static bool cifs_tcp_ses_needs_reconnect(struct TCP_Server_Info *server, int num_targets)
{
 spin_lock(&server->srv_lock);
 server->nr_targets = num_targets;
 if (server->tcpStatus == CifsExiting) {
  /* the demux thread will exit normally next time through the loop */
  spin_unlock(&server->srv_lock);
  wake_up(&server->response_q);
  return false;
 }

 cifs_dbg(FYI, "Mark tcp session as need reconnect\n");
 trace_smb3_reconnect(server->current_mid, server->conn_id,
        server->hostname);
 server->tcpStatus = CifsNeedReconnect;

 spin_unlock(&server->srv_lock);
 return true;
}

/*
 * cifs tcp session reconnection
 *
 * mark tcp session as reconnecting so temporarily locked
 * mark all smb sessions as reconnecting for tcp session
 * reconnect tcp session
 * wake up waiters on reconnection? - (not needed currently)
 *
 * if mark_smb_session is passed as true, unconditionally mark
 * the smb session (and tcon) for reconnect as well. This value
 * doesn't really matter for non-multichannel scenario.
 *
 */

static int __cifs_reconnect(struct TCP_Server_Info *server,
       bool mark_smb_session, bool once)
{
 int rc = 0;

 if (!cifs_tcp_ses_needs_reconnect(server, 1))
  return 0;

 /*
 * if smb session has been marked for reconnect, also reconnect all
 * connections. This way, the other connections do not end up bad.
 */

 if (mark_smb_session)
  cifs_signal_cifsd_for_reconnect(server, mark_smb_session);

 cifs_mark_tcp_ses_conns_for_reconnect(server, mark_smb_session);

 cifs_abort_connection(server);

 do {
  try_to_freeze();
  cifs_server_lock(server);

  if (!cifs_swn_set_server_dstaddr(server) &&
      !SERVER_IS_CHAN(server)) {
   /* resolve the hostname again to make sure that IP address is up-to-date */
   rc = reconn_set_ipaddr_from_hostname(server);
   cifs_dbg(FYI, "%s: reconn_set_ipaddr_from_hostname: rc=%d\n", __func__, rc);
  }

  if (cifs_rdma_enabled(server))
   rc = smbd_reconnect(server);
  else
   rc = generic_ip_connect(server);
  if (rc) {
   cifs_server_unlock(server);
   cifs_dbg(FYI, "%s: reconnect error %d\n", __func__, rc);
   /* If was asked to reconnect only once, do not try it more times */
   if (once)
    break;
   msleep(3000);
  } else {
   atomic_inc(&tcpSesReconnectCount);
   set_credits(server, 1);
   spin_lock(&server->srv_lock);
   if (server->tcpStatus != CifsExiting)
    server->tcpStatus = CifsNeedNegotiate;
   spin_unlock(&server->srv_lock);
   cifs_swn_reset_server_dstaddr(server);
   cifs_server_unlock(server);
   mod_delayed_work(cifsiod_wq, &server->reconnect, 0);
  }
 } while (server->tcpStatus == CifsNeedReconnect);

 spin_lock(&server->srv_lock);
 if (server->tcpStatus == CifsNeedNegotiate)
  mod_delayed_work(cifsiod_wq, &server->echo, 0);
 spin_unlock(&server->srv_lock);

 wake_up(&server->response_q);
 return rc;
}

#ifdef CONFIG_CIFS_DFS_UPCALL
static int __reconnect_target_locked(struct TCP_Server_Info *server,
         const char *target)
{
 int rc;
 char *hostname;

 if (!cifs_swn_set_server_dstaddr(server)) {
  if (server->hostname != target) {
   hostname = extract_hostname(target);
   if (!IS_ERR(hostname)) {
    spin_lock(&server->srv_lock);
    kfree(server->hostname);
    server->hostname = hostname;
    spin_unlock(&server->srv_lock);
   } else {
    cifs_dbg(FYI, "%s: couldn't extract hostname or address from dfs target: %ld\n",
      __func__, PTR_ERR(hostname));
    cifs_dbg(FYI, "%s: default to last target server: %s\n", __func__,
      server->hostname);
   }
  }
  /* resolve the hostname again to make sure that IP address is up-to-date. */
  rc = reconn_set_ipaddr_from_hostname(server);
  cifs_dbg(FYI, "%s: reconn_set_ipaddr_from_hostname: rc=%d\n", __func__, rc);
 }
 /* Reconnect the socket */
 if (cifs_rdma_enabled(server))
  rc = smbd_reconnect(server);
 else
  rc = generic_ip_connect(server);

 return rc;
}

static int reconnect_target_locked(struct TCP_Server_Info *server,
       struct dfs_cache_tgt_list *tl,
       struct dfs_cache_tgt_iterator **target_hint)
{
 struct dfs_cache_tgt_iterator *tit;
 int rc;

 *target_hint = NULL;

 /* If dfs target list is empty, then reconnect to last server */
 tit = dfs_cache_get_tgt_iterator(tl);
 if (!tit)
  return __reconnect_target_locked(server, server->hostname);

 /* Otherwise, try every dfs target in @tl */
 do {
  const char *target = dfs_cache_get_tgt_name(tit);

  spin_lock(&server->srv_lock);
  if (server->tcpStatus != CifsNeedReconnect) {
   spin_unlock(&server->srv_lock);
   return -ECONNRESET;
  }
  spin_unlock(&server->srv_lock);
  rc = __reconnect_target_locked(server, target);
  if (!rc) {
   *target_hint = tit;
   break;
  }
 } while ((tit = dfs_cache_get_next_tgt(tl, tit)));
 return rc;
}

static int reconnect_dfs_server(struct TCP_Server_Info *server)
{
 struct dfs_cache_tgt_iterator *target_hint = NULL;
 const char *ref_path = server->leaf_fullpath + 1;
 DFS_CACHE_TGT_LIST(tl);
 int num_targets = 0;
 int rc = 0;

 /*
 * Determine the number of dfs targets the referral path in @cifs_sb resolves to.
 *
 * smb2_reconnect() needs to know how long it should wait based upon the number of dfs
 * targets (server->nr_targets).  It's also possible that the cached referral was cleared
 * through /proc/fs/cifs/dfscache or the target list is empty due to server settings after
 * refreshing the referral, so, in this case, default it to 1.
 */

 if (!dfs_cache_noreq_find(ref_path, NULL, &tl))
  num_targets = dfs_cache_get_nr_tgts(&tl);
 if (!num_targets)
  num_targets = 1;

 if (!cifs_tcp_ses_needs_reconnect(server, num_targets))
  return 0;

 /*
 * Unconditionally mark all sessions & tcons for reconnect as we might be connecting to a
 * different server or share during failover.  It could be improved by adding some logic to
 * only do that in case it connects to a different server or share, though.
 */

 cifs_mark_tcp_ses_conns_for_reconnect(server, true);

 cifs_abort_connection(server);

 do {
  try_to_freeze();
  cifs_server_lock(server);

  rc = reconnect_target_locked(server, &tl, &target_hint);
  if (rc) {
   /* Failed to reconnect socket */
   cifs_server_unlock(server);
   cifs_dbg(FYI, "%s: reconnect error %d\n", __func__, rc);
   msleep(3000);
   continue;
  }
  /*
 * Socket was created.  Update tcp session status to CifsNeedNegotiate so that a
 * process waiting for reconnect will know it needs to re-establish session and tcon
 * through the reconnected target server.
 */

  atomic_inc(&tcpSesReconnectCount);
  set_credits(server, 1);
  spin_lock(&server->srv_lock);
  if (server->tcpStatus != CifsExiting)
   server->tcpStatus = CifsNeedNegotiate;
  spin_unlock(&server->srv_lock);
  cifs_swn_reset_server_dstaddr(server);
  cifs_server_unlock(server);
  mod_delayed_work(cifsiod_wq, &server->reconnect, 0);
 } while (server->tcpStatus == CifsNeedReconnect);

 dfs_cache_noreq_update_tgthint(ref_path, target_hint);
 dfs_cache_free_tgts(&tl);

 /* Need to set up echo worker again once connection has been established */
 spin_lock(&server->srv_lock);
 if (server->tcpStatus == CifsNeedNegotiate)
  mod_delayed_work(cifsiod_wq, &server->echo, 0);
 spin_unlock(&server->srv_lock);

 wake_up(&server->response_q);
 return rc;
}

static int
_cifs_reconnect(struct TCP_Server_Info *server, bool mark_smb_session, bool once)
{
 if (!server->leaf_fullpath)
  return __cifs_reconnect(server, mark_smb_session, once);
 return reconnect_dfs_server(server);
}
#else
static int
_cifs_reconnect(struct TCP_Server_Info *server, bool mark_smb_session, bool once)
{
 return __cifs_reconnect(server, mark_smb_session, once);
}
#endif

int
cifs_reconnect(struct TCP_Server_Info *server, bool mark_smb_session)
{
 return _cifs_reconnect(server, mark_smb_session, false);
}

static int
cifs_reconnect_once(struct TCP_Server_Info *server)
{
 return _cifs_reconnect(server, truetrue);
}

static void
cifs_echo_request(struct work_struct *work)
{
 int rc;
 struct TCP_Server_Info *server = container_of(work,
     struct TCP_Server_Info, echo.work);

 /*
 * We cannot send an echo if it is disabled.
 * Also, no need to ping if we got a response recently.
 */


 if (server->tcpStatus == CifsNeedReconnect ||
     server->tcpStatus == CifsExiting ||
     server->tcpStatus == CifsNew ||
     (server->ops->can_echo && !server->ops->can_echo(server)) ||
     time_before(jiffies, server->lstrp + server->echo_interval - HZ))
  goto requeue_echo;

 rc = server->ops->echo ? server->ops->echo(server) : -ENOSYS;
 cifs_server_dbg(FYI, "send echo request: rc = %d\n", rc);

 /* Check witness registrations */
 cifs_swn_check();

requeue_echo:
 queue_delayed_work(cifsiod_wq, &server->echo, server->echo_interval);
}

static bool
allocate_buffers(struct TCP_Server_Info *server)
{
 if (!server->bigbuf) {
  server->bigbuf = (char *)cifs_buf_get();
  if (!server->bigbuf) {
   cifs_server_dbg(VFS, "No memory for large SMB response\n");
   msleep(3000);
   /* retry will check if exiting */
   return false;
  }
 } else if (server->large_buf) {
  /* we are reusing a dirty large buf, clear its start */
  memset(server->bigbuf, 0, HEADER_SIZE(server));
 }

 if (!server->smallbuf) {
  server->smallbuf = (char *)cifs_small_buf_get();
  if (!server->smallbuf) {
   cifs_server_dbg(VFS, "No memory for SMB response\n");
   msleep(1000);
   /* retry will check if exiting */
   return false;
  }
  /* beginning of smb buffer is cleared in our buf_get */
 } else {
  /* if existing small buf clear beginning */
  memset(server->smallbuf, 0, HEADER_SIZE(server));
 }

 return true;
}

static bool
server_unresponsive(struct TCP_Server_Info *server)
{
 /*
 * If we're in the process of mounting a share or reconnecting a session
 * and the server abruptly shut down (e.g. socket wasn't closed, packet
 * had been ACK'ed but no SMB response), don't wait longer than 20s from
 * when negotiate actually started.
 */

 spin_lock(&server->srv_lock);
 if (server->tcpStatus == CifsInNegotiate &&
     time_after(jiffies, server->neg_start + 20 * HZ)) {
  spin_unlock(&server->srv_lock);
  cifs_reconnect(server, false);
  return true;
 }
 /*
 * We need to wait 3 echo intervals to make sure we handle such
 * situations right:
 * 1s  client sends a normal SMB request
 * 2s  client gets a response
 * 30s echo workqueue job pops, and decides we got a response recently
 *     and don't need to send another
 * ...
 * 65s kernel_recvmsg times out, and we see that we haven't gotten
 *     a response in >60s.
 */

 if ((server->tcpStatus == CifsGood ||
     server->tcpStatus == CifsNeedNegotiate) &&
     (!server->ops->can_echo || server->ops->can_echo(server)) &&
     time_after(jiffies, server->lstrp + 3 * server->echo_interval)) {
  spin_unlock(&server->srv_lock);
  cifs_server_dbg(VFS, "has not responded in %lu seconds. Reconnecting...\n",
    (3 * server->echo_interval) / HZ);
  cifs_reconnect(server, false);
  return true;
 }
 spin_unlock(&server->srv_lock);

 return false;
}

static inline bool
zero_credits(struct TCP_Server_Info *server)
{
 int val;

 spin_lock(&server->req_lock);
 val = server->credits + server->echo_credits + server->oplock_credits;
 if (server->in_flight == 0 && val == 0) {
  spin_unlock(&server->req_lock);
  return true;
 }
 spin_unlock(&server->req_lock);
 return false;
}

static int
cifs_readv_from_socket(struct TCP_Server_Info *server, struct msghdr *smb_msg)
{
 int length = 0;
 int total_read;

 for (total_read = 0; msg_data_left(smb_msg); total_read += length) {
  try_to_freeze();

  /* reconnect if no credits and no requests in flight */
  if (zero_credits(server)) {
   cifs_reconnect(server, false);
   return -ECONNABORTED;
  }

  if (server_unresponsive(server))
   return -ECONNABORTED;
  if (cifs_rdma_enabled(server) && server->smbd_conn)
   length = smbd_recv(server->smbd_conn, smb_msg);
  else
   length = sock_recvmsg(server->ssocket, smb_msg, 0);

  spin_lock(&server->srv_lock);
  if (server->tcpStatus == CifsExiting) {
   spin_unlock(&server->srv_lock);
   return -ESHUTDOWN;
  }

  if (server->tcpStatus == CifsNeedReconnect) {
   spin_unlock(&server->srv_lock);
   cifs_reconnect(server, false);
   return -ECONNABORTED;
  }
  spin_unlock(&server->srv_lock);

  if (length == -ERESTARTSYS ||
      length == -EAGAIN ||
      length == -EINTR) {
   /*
 * Minimum sleep to prevent looping, allowing socket
 * to clear and app threads to set tcpStatus
 * CifsNeedReconnect if server hung.
 */

   usleep_range(1000, 2000);
   length = 0;
   continue;
  }

  if (length <= 0) {
   cifs_dbg(FYI, "Received no data or error: %d\n", length);
   cifs_reconnect(server, false);
   return -ECONNABORTED;
  }
 }
 return total_read;
}

int
cifs_read_from_socket(struct TCP_Server_Info *server, char *buf,
        unsigned int to_read)
{
 struct msghdr smb_msg = {};
 struct kvec iov = {.iov_base = buf, .iov_len = to_read};

 iov_iter_kvec(&smb_msg.msg_iter, ITER_DEST, &iov, 1, to_read);

 return cifs_readv_from_socket(server, &smb_msg);
}

ssize_t
cifs_discard_from_socket(struct TCP_Server_Info *server, size_t to_read)
{
 struct msghdr smb_msg = {};

 /*
 *  iov_iter_discard already sets smb_msg.type and count and iov_offset
 *  and cifs_readv_from_socket sets msg_control and msg_controllen
 *  so little to initialize in struct msghdr
 */

 iov_iter_discard(&smb_msg.msg_iter, ITER_DEST, to_read);

 return cifs_readv_from_socket(server, &smb_msg);
}

int
cifs_read_iter_from_socket(struct TCP_Server_Info *server, struct iov_iter *iter,
      unsigned int to_read)
{
 struct msghdr smb_msg = { .msg_iter = *iter };

 iov_iter_truncate(&smb_msg.msg_iter, to_read);
 return cifs_readv_from_socket(server, &smb_msg);
}

static bool
is_smb_response(struct TCP_Server_Info *server, unsigned char type)
{
 /*
 * The first byte big endian of the length field,
 * is actually not part of the length but the type
 * with the most common, zero, as regular data.
 */

 switch (type) {
 case RFC1002_SESSION_MESSAGE:
  /* Regular SMB response */
  return true;
 case RFC1002_SESSION_KEEP_ALIVE:
  /*
 * RFC 1002 session keep alive can sent by the server only when
 * we established a RFC 1002 session. But Samba servers send
 * RFC 1002 session keep alive also over port 445 on which
 * RFC 1002 session is not established.
 */

  cifs_dbg(FYI, "RFC 1002 session keep alive\n");
  break;
 case RFC1002_POSITIVE_SESSION_RESPONSE:
  /*
 * RFC 1002 positive session response cannot be returned
 * for SMB request. RFC 1002 session response is handled
 * exclusively in ip_rfc1001_connect() function.
 */

  cifs_server_dbg(VFS, "RFC 1002 positive session response (unexpected)\n");
  cifs_reconnect(server, true);
  break;
 case RFC1002_NEGATIVE_SESSION_RESPONSE:
  /*
 * We get this from Windows 98 instead of an error on
 * SMB negprot response, when we have not established
 * RFC 1002 session (which means ip_rfc1001_connect()
 * was skipped). Note that same still happens with
 * Windows Server 2022 when connecting via port 139.
 * So for this case when mount option -o nonbsessinit
 * was not specified, try to reconnect with establishing
 * RFC 1002 session. If new socket establishment with
 * RFC 1002 session was successful then return to the
 * mid's caller -EAGAIN, so it can retry the request.
 */

  if (!cifs_rdma_enabled(server) &&
      server->tcpStatus == CifsInNegotiate &&
      !server->with_rfc1001 &&
      server->rfc1001_sessinit != 0) {
   int rc, mid_rc;
   struct mid_q_entry *mid, *nmid;
   LIST_HEAD(dispose_list);

   cifs_dbg(FYI, "RFC 1002 negative session response during SMB Negotiate, retrying with NetBIOS session\n");

   /*
 * Before reconnect, delete all pending mids for this
 * server, so reconnect would not signal connection
 * aborted error to mid's callbacks. Note that for this
 * server there should be exactly one pending mid
 * corresponding to SMB1/SMB2 Negotiate packet.
 */

   spin_lock(&server->mid_queue_lock);
   list_for_each_entry_safe(mid, nmid, &server->pending_mid_q, qhead) {
    kref_get(&mid->refcount);
    list_move(&mid->qhead, &dispose_list);
    mid->deleted_from_q = true;
   }
   spin_unlock(&server->mid_queue_lock);

   /* Now try to reconnect once with NetBIOS session. */
   server->with_rfc1001 = true;
   rc = cifs_reconnect_once(server);

   /*
 * If reconnect was successful then indicate -EAGAIN
 * to mid's caller. If reconnect failed with -EAGAIN
 * then mask it as -EHOSTDOWN, so mid's caller would
 * know that it failed.
 */

   if (rc == 0)
    mid_rc = -EAGAIN;
   else if (rc == -EAGAIN)
    mid_rc = -EHOSTDOWN;
   else
    mid_rc = rc;

   /*
 * After reconnect (either successful or unsuccessful)
 * deliver reconnect status to mid's caller via mid's
 * callback. Use MID_RC state which indicates that the
 * return code should be read from mid_rc member.
 */

   list_for_each_entry_safe(mid, nmid, &dispose_list, qhead) {
    list_del_init(&mid->qhead);
    mid->mid_rc = mid_rc;
    mid->mid_state = MID_RC;
    mid_execute_callback(mid);
    release_mid(mid);
   }

   /*
 * If reconnect failed then wait two seconds. In most
 * cases we were been called from the mount context and
 * delivered failure to mid's callback will stop this
 * receiver task thread and fails the mount process.
 * So wait two seconds to prevent another reconnect
 * in this task thread, which would be useless as the
 * mount context will fail at all.
 */

   if (rc != 0)
    msleep(2000);
  } else {
   cifs_server_dbg(VFS, "RFC 1002 negative session response (unexpected)\n");
   cifs_reconnect(server, true);
  }
  break;
 case RFC1002_RETARGET_SESSION_RESPONSE:
  cifs_server_dbg(VFS, "RFC 1002 retarget session response (unexpected)\n");
  cifs_reconnect(server, true);
  break;
 default:
  cifs_server_dbg(VFS, "RFC 1002 unknown response type 0x%x\n", type);
  cifs_reconnect(server, true);
 }

 return false;
}

void
dequeue_mid(struct mid_q_entry *mid, bool malformed)
{
#ifdef CONFIG_CIFS_STATS2
 mid->when_received = jiffies;
#endif
 spin_lock(&mid->server->mid_queue_lock);
 if (!malformed)
  mid->mid_state = MID_RESPONSE_RECEIVED;
 else
  mid->mid_state = MID_RESPONSE_MALFORMED;
 /*
 * Trying to handle/dequeue a mid after the send_recv()
 * function has finished processing it is a bug.
 */

 if (mid->deleted_from_q == true) {
  spin_unlock(&mid->server->mid_queue_lock);
  pr_warn_once("trying to dequeue a deleted mid\n");
 } else {
  list_del_init(&mid->qhead);
  mid->deleted_from_q = true;
  spin_unlock(&mid->server->mid_queue_lock);
 }
}

static unsigned int
smb2_get_credits_from_hdr(char *buffer, struct TCP_Server_Info *server)
{
 struct smb2_hdr *shdr = (struct smb2_hdr *)buffer;

 /*
 * SMB1 does not use credits.
 */

 if (is_smb1(server))
  return 0;

 return le16_to_cpu(shdr->CreditRequest);
}

static void
handle_mid(struct mid_q_entry *mid, struct TCP_Server_Info *server,
    char *buf, int malformed)
{
 if (server->ops->check_trans2 &&
     server->ops->check_trans2(mid, server, buf, malformed))
  return;
 mid->credits_received = smb2_get_credits_from_hdr(buf, server);
 mid->resp_buf = buf;
 mid->large_buf = server->large_buf;
 /* Was previous buf put in mpx struct for multi-rsp? */
 if (!mid->multiRsp) {
  /* smb buffer will be freed by user thread */
  if (server->large_buf)
   server->bigbuf = NULL;
  else
   server->smallbuf = NULL;
 }
 dequeue_mid(mid, malformed);
}

int
cifs_enable_signing(struct TCP_Server_Info *server, bool mnt_sign_required)
{
 bool srv_sign_required = server->sec_mode & server->vals->signing_required;
 bool srv_sign_enabled = server->sec_mode & server->vals->signing_enabled;
 bool mnt_sign_enabled;

 /*
 * Is signing required by mnt options? If not then check
 * global_secflags to see if it is there.
 */

 if (!mnt_sign_required)
  mnt_sign_required = ((global_secflags & CIFSSEC_MUST_SIGN) ==
      CIFSSEC_MUST_SIGN);

 /*
 * If signing is required then it's automatically enabled too,
 * otherwise, check to see if the secflags allow it.
 */

 mnt_sign_enabled = mnt_sign_required ? mnt_sign_required :
    (global_secflags & CIFSSEC_MAY_SIGN);

 /* If server requires signing, does client allow it? */
 if (srv_sign_required) {
  if (!mnt_sign_enabled) {
   cifs_dbg(VFS, "Server requires signing, but it's disabled in SecurityFlags!\n");
   return -EOPNOTSUPP;
  }
  server->sign = true;
 }

 /* If client requires signing, does server allow it? */
 if (mnt_sign_required) {
  if (!srv_sign_enabled) {
   cifs_dbg(VFS, "Server does not support signing!\n");
   return -EOPNOTSUPP;
  }
  server->sign = true;
 }

 if (cifs_rdma_enabled(server) && server->sign)
  cifs_dbg(VFS, "Signing is enabled, and RDMA read/write will be disabled\n");

 return 0;
}

static noinline_for_stack void
clean_demultiplex_info(struct TCP_Server_Info *server)
{
 int length;

 /* take it off the list, if it's not already */
 spin_lock(&server->srv_lock);
 list_del_init(&server->tcp_ses_list);
 spin_unlock(&server->srv_lock);

 cancel_delayed_work_sync(&server->echo);

 spin_lock(&server->srv_lock);
 server->tcpStatus = CifsExiting;
 spin_unlock(&server->srv_lock);
 wake_up_all(&server->response_q);

 /* check if we have blocked requests that need to free */
 spin_lock(&server->req_lock);
 if (server->credits <= 0)
  server->credits = 1;
 spin_unlock(&server->req_lock);
 /*
 * Although there should not be any requests blocked on this queue it
 * can not hurt to be paranoid and try to wake up requests that may
 * haven been blocked when more than 50 at time were on the wire to the
 * same server - they now will see the session is in exit state and get
 * out of SendReceive.
 */

 wake_up_all(&server->request_q);
 /* give those requests time to exit */
 msleep(125);
 if (cifs_rdma_enabled(server))
  smbd_destroy(server);
 if (server->ssocket) {
  sock_release(server->ssocket);
  server->ssocket = NULL;
 }

 if (!list_empty(&server->pending_mid_q)) {
  struct mid_q_entry *mid_entry;
  struct list_head *tmp, *tmp2;
  LIST_HEAD(dispose_list);

  spin_lock(&server->mid_queue_lock);
  list_for_each_safe(tmp, tmp2, &server->pending_mid_q) {
   mid_entry = list_entry(tmp, struct mid_q_entry, qhead);
   cifs_dbg(FYI, "Clearing mid %llu\n", mid_entry->mid);
   kref_get(&mid_entry->refcount);
   mid_entry->mid_state = MID_SHUTDOWN;
   list_move(&mid_entry->qhead, &dispose_list);
   mid_entry->deleted_from_q = true;
  }
  spin_unlock(&server->mid_queue_lock);

  /* now walk dispose list and issue callbacks */
  list_for_each_safe(tmp, tmp2, &dispose_list) {
   mid_entry = list_entry(tmp, struct mid_q_entry, qhead);
   cifs_dbg(FYI, "Callback mid %llu\n", mid_entry->mid);
   list_del_init(&mid_entry->qhead);
   mid_execute_callback(mid_entry);
   release_mid(mid_entry);
  }
  /* 1/8th of sec is more than enough time for them to exit */
  msleep(125);
 }

 if (!list_empty(&server->pending_mid_q)) {
  /*
 * mpx threads have not exited yet give them at least the smb
 * send timeout time for long ops.
 *
 * Due to delays on oplock break requests, we need to wait at
 * least 45 seconds before giving up on a request getting a
 * response and going ahead and killing cifsd.
 */

  cifs_dbg(FYI, "Wait for exit from demultiplex thread\n");
  msleep(46000);
  /*
 * If threads still have not exited they are probably never
 * coming home not much else we can do but free the memory.
 */

 }

 put_net(cifs_net_ns(server));
 kfree(server->leaf_fullpath);
 kfree(server->hostname);
 kfree(server);

 length = atomic_dec_return(&tcpSesAllocCount);
 if (length > 0)
  mempool_resize(cifs_req_poolp, length + cifs_min_rcv);
}

static int
standard_receive3(struct TCP_Server_Info *server, struct mid_q_entry *mid)
{
 int length;
 char *buf = server->smallbuf;
 unsigned int pdu_length = server->pdu_size;

 /* make sure this will fit in a large buffer */
 if (pdu_length > CIFSMaxBufSize + MAX_HEADER_SIZE(server) -
     HEADER_PREAMBLE_SIZE(server)) {
  cifs_server_dbg(VFS, "SMB response too long (%u bytes)\n", pdu_length);
  cifs_reconnect(server, true);
  return -ECONNABORTED;
 }

 /* switch to large buffer if too big for a small one */
 if (pdu_length > MAX_CIFS_SMALL_BUFFER_SIZE - 4) {
  server->large_buf = true;
  memcpy(server->bigbuf, buf, server->total_read);
  buf = server->bigbuf;
 }

 /* now read the rest */
 length = cifs_read_from_socket(server, buf + HEADER_SIZE(server) - 1,
           pdu_length - MID_HEADER_SIZE(server));

 if (length < 0)
  return length;
 server->total_read += length;

 dump_smb(buf, server->total_read);

 return cifs_handle_standard(server, mid);
}

int
cifs_handle_standard(struct TCP_Server_Info *server, struct mid_q_entry *mid)
{
 char *buf = server->large_buf ? server->bigbuf : server->smallbuf;
 int rc;

 /*
 * We know that we received enough to get to the MID as we
 * checked the pdu_length earlier. Now check to see
 * if the rest of the header is OK.
 *
 * 48 bytes is enough to display the header and a little bit
 * into the payload for debugging purposes.
 */

 rc = server->ops->check_message(buf, server->total_read, server);
 if (rc)
  cifs_dump_mem("Bad SMB: ", buf,
   min_t(unsigned int, server->total_read, 48));

 if (server->ops->is_session_expired &&
     server->ops->is_session_expired(buf)) {
  cifs_reconnect(server, true);
  return -1;
 }

 if (server->ops->is_status_pending &&
     server->ops->is_status_pending(buf, server))
  return -1;

 if (!mid)
  return rc;

 handle_mid(mid, server, buf, rc);
 return 0;
}

static void
smb2_add_credits_from_hdr(char *buffer, struct TCP_Server_Info *server)
{
 struct smb2_hdr *shdr = (struct smb2_hdr *)buffer;
 int scredits, in_flight;

 /*
 * SMB1 does not use credits.
 */

 if (is_smb1(server))
  return;

 if (shdr->CreditRequest) {
  spin_lock(&server->req_lock);
  server->credits += le16_to_cpu(shdr->CreditRequest);
  scredits = server->credits;
  in_flight = server->in_flight;
  spin_unlock(&server->req_lock);
  wake_up(&server->request_q);

  trace_smb3_hdr_credits(server->current_mid,
    server->conn_id, server->hostname, scredits,
    le16_to_cpu(shdr->CreditRequest), in_flight);
  cifs_server_dbg(FYI, "%s: added %u credits total=%d\n",
    __func__, le16_to_cpu(shdr->CreditRequest),
    scredits);
 }
}


static int
cifs_demultiplex_thread(void *p)
{
 int i, num_mids, length;
 struct TCP_Server_Info *server = p;
 unsigned int pdu_length;
 unsigned int next_offset;
 char *buf = NULL;
 struct task_struct *task_to_wake = NULL;
 struct mid_q_entry *mids[MAX_COMPOUND];
 char *bufs[MAX_COMPOUND];
 unsigned int noreclaim_flag, num_io_timeout = 0;
 bool pending_reconnect = false;

 noreclaim_flag = memalloc_noreclaim_save();
 cifs_dbg(FYI, "Demultiplex PID: %d\n", task_pid_nr(current));

 length = atomic_inc_return(&tcpSesAllocCount);
 if (length > 1)
  mempool_resize(cifs_req_poolp, length + cifs_min_rcv);

 set_freezable();
 allow_kernel_signal(SIGKILL);
 while (server->tcpStatus != CifsExiting) {
  if (try_to_freeze())
   continue;

  if (!allocate_buffers(server))
   continue;

  server->large_buf = false;
  buf = server->smallbuf;
  pdu_length = 4; /* enough to get RFC1001 header */

  length = cifs_read_from_socket(server, buf, pdu_length);
  if (length < 0)
   continue;

  if (is_smb1(server))
   server->total_read = length;
  else
   server->total_read = 0;

  /*
 * The right amount was read from socket - 4 bytes,
 * so we can now interpret the length field.
 */

  pdu_length = get_rfc1002_length(buf);

  cifs_dbg(FYI, "RFC1002 header 0x%x\n", pdu_length);
  if (!is_smb_response(server, buf[0]))
   continue;

  pending_reconnect = false;
next_pdu:
  server->pdu_size = pdu_length;

  /* make sure we have enough to get to the MID */
  if (server->pdu_size < MID_HEADER_SIZE(server)) {
   cifs_server_dbg(VFS, "SMB response too short (%u bytes)\n",
     server->pdu_size);
   cifs_reconnect(server, true);
   continue;
  }

  /* read down to the MID */
  length = cifs_read_from_socket(server,
        buf + HEADER_PREAMBLE_SIZE(server),
        MID_HEADER_SIZE(server));
  if (length < 0)
   continue;
  server->total_read += length;

  if (server->ops->next_header) {
   if (server->ops->next_header(server, buf, &next_offset)) {
    cifs_dbg(VFS, "%s: malformed response (next_offset=%u)\n",
      __func__, next_offset);
    cifs_reconnect(server, true);
    continue;
   }
   if (next_offset)
    server->pdu_size = next_offset;
  }

  memset(mids, 0, sizeof(mids));
  memset(bufs, 0, sizeof(bufs));
  num_mids = 0;

  if (server->ops->is_transform_hdr &&
      server->ops->receive_transform &&
      server->ops->is_transform_hdr(buf)) {
   length = server->ops->receive_transform(server,
        mids,
        bufs,
        &num_mids);
  } else {
   mids[0] = server->ops->find_mid(server, buf);
   bufs[0] = buf;
   num_mids = 1;

   if (!mids[0] || !mids[0]->receive)
    length = standard_receive3(server, mids[0]);
   else
    length = mids[0]->receive(server, mids[0]);
  }

  if (length < 0) {
   for (i = 0; i < num_mids; i++)
    if (mids[i])
     release_mid(mids[i]);
   continue;
  }

  if (server->ops->is_status_io_timeout &&
      server->ops->is_status_io_timeout(buf)) {
   num_io_timeout++;
   if (num_io_timeout > MAX_STATUS_IO_TIMEOUT) {
    cifs_server_dbg(VFS,
      "Number of request timeouts exceeded %d. Reconnecting",
      MAX_STATUS_IO_TIMEOUT);

    pending_reconnect = true;
    num_io_timeout = 0;
   }
  }

  server->lstrp = jiffies;

  for (i = 0; i < num_mids; i++) {
   if (mids[i] != NULL) {
    mids[i]->resp_buf_size = server->pdu_size;

    if (bufs[i] != NULL) {
     if (server->ops->is_network_name_deleted &&
         server->ops->is_network_name_deleted(bufs[i],
           server)) {
      cifs_server_dbg(FYI,
        "Share deleted. Reconnect needed");
     }
    }

    if (!mids[i]->multiRsp || mids[i]->multiEnd)
     mid_execute_callback(mids[i]);

    release_mid(mids[i]);
   } else if (server->ops->is_oplock_break &&
       server->ops->is_oplock_break(bufs[i],
        server)) {
    smb2_add_credits_from_hdr(bufs[i], server);
    cifs_dbg(FYI, "Received oplock break\n");
   } else {
    cifs_server_dbg(VFS, "No task to wake, unknown frame received! NumMids %d\n",
      atomic_read(&mid_count));
    cifs_dump_mem("Received Data is: ", bufs[i],
           HEADER_SIZE(server));
    smb2_add_credits_from_hdr(bufs[i], server);
#ifdef CONFIG_CIFS_DEBUG2
    if (server->ops->dump_detail)
     server->ops->dump_detail(bufs[i],
         server);
    cifs_dump_mids(server);
#endif /* CIFS_DEBUG2 */
   }
  }

  if (pdu_length > server->pdu_size) {
   if (!allocate_buffers(server))
    continue;
   pdu_length -= server->pdu_size;
   server->total_read = 0;
   server->large_buf = false;
   buf = server->smallbuf;
   goto next_pdu;
  }

  /* do this reconnect at the very end after processing all MIDs */
  if (pending_reconnect)
   cifs_reconnect(server, true);

 } /* end while !EXITING */

 /* buffer usually freed in free_mid - need to free it here on exit */
 cifs_buf_release(server->bigbuf);
 if (server->smallbuf) /* no sense logging a debug message if NULL */
  cifs_small_buf_release(server->smallbuf);

 task_to_wake = xchg(&server->tsk, NULL);
 clean_demultiplex_info(server);

 /* if server->tsk was NULL then wait for a signal before exiting */
 if (!task_to_wake) {
  set_current_state(TASK_INTERRUPTIBLE);
  while (!signal_pending(current)) {
   schedule();
   set_current_state(TASK_INTERRUPTIBLE);
  }
  set_current_state(TASK_RUNNING);
 }

 memalloc_noreclaim_restore(noreclaim_flag);
 module_put_and_kthread_exit(0);
}

int
cifs_ipaddr_cmp(struct sockaddr *srcaddr, struct sockaddr *rhs)
{
 struct sockaddr_in *saddr4 = (struct sockaddr_in *)srcaddr;
 struct sockaddr_in *vaddr4 = (struct sockaddr_in *)rhs;
 struct sockaddr_in6 *saddr6 = (struct sockaddr_in6 *)srcaddr;
 struct sockaddr_in6 *vaddr6 = (struct sockaddr_in6 *)rhs;

 switch (srcaddr->sa_family) {
 case AF_UNSPEC:
  switch (rhs->sa_family) {
  case AF_UNSPEC:
   return 0;
  case AF_INET:
  case AF_INET6:
   return 1;
  default:
   return -1;
  }
 case AF_INET: {
  switch (rhs->sa_family) {
  case AF_UNSPEC:
   return -1;
  case AF_INET:
   return memcmp(saddr4, vaddr4,
          sizeof(struct sockaddr_in));
  case AF_INET6:
   return 1;
  default:
   return -1;
  }
 }
 case AF_INET6: {
  switch (rhs->sa_family) {
  case AF_UNSPEC:
  case AF_INET:
   return -1;
  case AF_INET6:
   return memcmp(saddr6,
          vaddr6,
          sizeof(struct sockaddr_in6));
  default:
   return -1;
  }
 }
 default:
  return -1; /* don't expect to be here */
 }
}

/*
 * Returns true if srcaddr isn't specified and rhs isn't specified, or
 * if srcaddr is specified and matches the IP address of the rhs argument
 */

bool
cifs_match_ipaddr(struct sockaddr *srcaddr, struct sockaddr *rhs)
{
 switch (srcaddr->sa_family) {
 case AF_UNSPEC:
  return (rhs->sa_family == AF_UNSPEC);
 case AF_INET: {
  struct sockaddr_in *saddr4 = (struct sockaddr_in *)srcaddr;
  struct sockaddr_in *vaddr4 = (struct sockaddr_in *)rhs;

  return (saddr4->sin_addr.s_addr == vaddr4->sin_addr.s_addr);
 }
 case AF_INET6: {
  struct sockaddr_in6 *saddr6 = (struct sockaddr_in6 *)srcaddr;
  struct sockaddr_in6 *vaddr6 = (struct sockaddr_in6 *)rhs;

  return (ipv6_addr_equal(&saddr6->sin6_addr, &vaddr6->sin6_addr)
   && saddr6->sin6_scope_id == vaddr6->sin6_scope_id);
 }
 default:
  WARN_ON(1);
  return false/* don't expect to be here */
 }
}

/*
 * If no port is specified in addr structure, we try to match with 445 port
 * and if it fails - with 139 ports. It should be called only if address
 * families of server and addr are equal.
 */

static bool
match_port(struct TCP_Server_Info *server, struct sockaddr *addr)
{
 __be16 port, *sport;

 /* SMBDirect manages its own ports, don't match it here */
 if (server->rdma)
  return true;

 switch (addr->sa_family) {
 case AF_INET:
  sport = &((struct sockaddr_in *) &server->dstaddr)->sin_port;
  port = ((struct sockaddr_in *) addr)->sin_port;
  break;
 case AF_INET6:
  sport = &((struct sockaddr_in6 *) &server->dstaddr)->sin6_port;
  port = ((struct sockaddr_in6 *) addr)->sin6_port;
  break;
 default:
  WARN_ON(1);
  return false;
 }

 if (!port) {
  port = htons(CIFS_PORT);
  if (port == *sport)
   return true;

  port = htons(RFC1001_PORT);
 }

 return port == *sport;
}

static bool match_server_address(struct TCP_Server_Info *server, struct sockaddr *addr)
{
 if (!cifs_match_ipaddr(addr, (struct sockaddr *)&server->dstaddr))
  return false;

 return true;
}

static bool
match_security(struct TCP_Server_Info *server, struct smb3_fs_context *ctx)
{
 /*
 * The select_sectype function should either return the ctx->sectype
 * that was specified, or "Unspecified" if that sectype was not
 * compatible with the given NEGOTIATE request.
 */

 if (server->ops->select_sectype(server, ctx->sectype)
      == Unspecified)
  return false;

 /*
 * Now check if signing mode is acceptable. No need to check
 * global_secflags at this point since if MUST_SIGN is set then
 * the server->sign had better be too.
 */

 if (ctx->sign && !server->sign)
  return false;

 return true;
}

/* this function must be called with srv_lock held */
static int match_server(struct TCP_Server_Info *server,
   struct smb3_fs_context *ctx,
   bool match_super)
{
 struct sockaddr *addr = (struct sockaddr *)&ctx->dstaddr;

 lockdep_assert_held(&server->srv_lock);

 if (ctx->nosharesock)
  return 0;

 /* this server does not share socket */
 if (server->nosharesock)
  return 0;

 if (!match_super && (ctx->dfs_conn || server->dfs_conn))
  return 0;

 /* If multidialect negotiation see if existing sessions match one */
 if (strcmp(ctx->vals->version_string, SMB3ANY_VERSION_STRING) == 0) {
  if (server->vals->protocol_id < SMB30_PROT_ID)
   return 0;
 } else if (strcmp(ctx->vals->version_string,
     SMBDEFAULT_VERSION_STRING) == 0) {
  if (server->vals->protocol_id < SMB21_PROT_ID)
   return 0;
 } else if ((server->vals != ctx->vals) || (server->ops != ctx->ops))
  return 0;

 if (!net_eq(cifs_net_ns(server), current->nsproxy->net_ns))
  return 0;

 if (!cifs_match_ipaddr((struct sockaddr *)&ctx->srcaddr,
          (struct sockaddr *)&server->srcaddr))
  return 0;

 if (strcasecmp(server->hostname, ctx->server_hostname) ||
     !match_server_address(server, addr) ||
     !match_port(server, addr))
  return 0;

 if (!match_security(server, ctx))
  return 0;

 if (server->echo_interval != ctx->echo_interval * HZ)
  return 0;

 if (server->rdma != ctx->rdma)
  return 0;

 if (server->ignore_signature != ctx->ignore_signature)
  return 0;

 if (server->min_offload != ctx->min_offload)
  return 0;

 if (server->retrans != ctx->retrans)
  return 0;

 return 1;
}

struct TCP_Server_Info *
cifs_find_tcp_session(struct smb3_fs_context *ctx)
{
 struct TCP_Server_Info *server;

 spin_lock(&cifs_tcp_ses_lock);
 list_for_each_entry(server, &cifs_tcp_ses_list, tcp_ses_list) {
  spin_lock(&server->srv_lock);
  /*
 * Skip ses channels since they're only handled in lower layers
 * (e.g. cifs_send_recv).
 */

  if (SERVER_IS_CHAN(server) ||
      !match_server(server, ctx, false)) {
   spin_unlock(&server->srv_lock);
   continue;
  }
  spin_unlock(&server->srv_lock);

  ++server->srv_count;
  spin_unlock(&cifs_tcp_ses_lock);
  cifs_dbg(FYI, "Existing tcp session with server found\n");
  return server;
 }
 spin_unlock(&cifs_tcp_ses_lock);
 return NULL;
}

void
cifs_put_tcp_session(struct TCP_Server_Info *server, int from_reconnect)
{
 struct task_struct *task;

 spin_lock(&cifs_tcp_ses_lock);
 if (--server->srv_count > 0) {
  spin_unlock(&cifs_tcp_ses_lock);
  return;
 }

 /* srv_count can never go negative */
 WARN_ON(server->srv_count < 0);

 list_del_init(&server->tcp_ses_list);
 spin_unlock(&cifs_tcp_ses_lock);

 cancel_delayed_work_sync(&server->echo);

 if (from_reconnect)
  /*
 * Avoid deadlock here: reconnect work calls
 * cifs_put_tcp_session() at its end. Need to be sure
 * that reconnect work does nothing with server pointer after
 * that step.
 */

  cancel_delayed_work(&server->reconnect);
 else
  cancel_delayed_work_sync(&server->reconnect);

 /* For secondary channels, we pick up ref-count on the primary server */
 if (SERVER_IS_CHAN(server))
  cifs_put_tcp_session(server->primary_server, from_reconnect);

 spin_lock(&server->srv_lock);
 server->tcpStatus = CifsExiting;
 spin_unlock(&server->srv_lock);

 cifs_crypto_secmech_release(server);

 kfree_sensitive(server->session_key.response);
 server->session_key.response = NULL;
 server->session_key.len = 0;

 task = xchg(&server->tsk, NULL);
 if (task)
  send_sig(SIGKILL, task, 1);
}

struct TCP_Server_Info *
cifs_get_tcp_session(struct smb3_fs_context *ctx,
       struct TCP_Server_Info *primary_server)
{
 struct TCP_Server_Info *tcp_ses = NULL;
 int rc;

 cifs_dbg(FYI, "UNC: %s\n", ctx->UNC);

 /* see if we already have a matching tcp_ses */
 tcp_ses = cifs_find_tcp_session(ctx);
 if (tcp_ses)
  return tcp_ses;

 tcp_ses = kzalloc(sizeof(struct TCP_Server_Info), GFP_KERNEL);
 if (!tcp_ses) {
  rc = -ENOMEM;
  goto out_err;
 }

 tcp_ses->hostname = kstrdup(ctx->server_hostname, GFP_KERNEL);
 if (!tcp_ses->hostname) {
  rc = -ENOMEM;
  goto out_err;
 }

 if (ctx->leaf_fullpath) {
  tcp_ses->leaf_fullpath = kstrdup(ctx->leaf_fullpath, GFP_KERNEL);
  if (!tcp_ses->leaf_fullpath) {
   rc = -ENOMEM;
   goto out_err;
  }
 }
 if (ctx->dns_dom)
  strscpy(tcp_ses->dns_dom, ctx->dns_dom);

 if (ctx->nosharesock)
  tcp_ses->nosharesock = true;
 tcp_ses->dfs_conn = ctx->dfs_conn;

 tcp_ses->ops = ctx->ops;
 tcp_ses->vals = ctx->vals;
 cifs_set_net_ns(tcp_ses, get_net(current->nsproxy->net_ns));

 tcp_ses->sign = ctx->sign;
 tcp_ses->conn_id = atomic_inc_return(&tcpSesNextId);
 tcp_ses->noblockcnt = ctx->rootfs;
 tcp_ses->noblocksnd = ctx->noblocksnd || ctx->rootfs;
 tcp_ses->noautotune = ctx->noautotune;
 tcp_ses->tcp_nodelay = ctx->sockopt_tcp_nodelay;
 tcp_ses->rdma = ctx->rdma;
 tcp_ses->in_flight = 0;
 tcp_ses->max_in_flight = 0;
 tcp_ses->credits = 1;
 if (primary_server) {
  spin_lock(&cifs_tcp_ses_lock);
  ++primary_server->srv_count;
  spin_unlock(&cifs_tcp_ses_lock);
  tcp_ses->primary_server = primary_server;
 }
 init_waitqueue_head(&tcp_ses->response_q);
 init_waitqueue_head(&tcp_ses->request_q);
 INIT_LIST_HEAD(&tcp_ses->pending_mid_q);
 mutex_init(&tcp_ses->_srv_mutex);
 memcpy(tcp_ses->workstation_RFC1001_name,
  ctx->source_rfc1001_name, RFC1001_NAME_LEN_WITH_NULL);
 memcpy(tcp_ses->server_RFC1001_name,
  ctx->target_rfc1001_name, RFC1001_NAME_LEN_WITH_NULL);
 tcp_ses->rfc1001_sessinit = ctx->rfc1001_sessinit;
 tcp_ses->with_rfc1001 = false;
 tcp_ses->session_estab = false;
 tcp_ses->sequence_number = 0;
 tcp_ses->channel_sequence_num = 0; /* only tracked for primary channel */
 tcp_ses->reconnect_instance = 1;
 tcp_ses->lstrp = jiffies;
 tcp_ses->compression.requested = ctx->compress;
 spin_lock_init(&tcp_ses->req_lock);
 spin_lock_init(&tcp_ses->srv_lock);
 spin_lock_init(&tcp_ses->mid_queue_lock);
 spin_lock_init(&tcp_ses->mid_counter_lock);
 INIT_LIST_HEAD(&tcp_ses->tcp_ses_list);
 INIT_LIST_HEAD(&tcp_ses->smb_ses_list);
 INIT_DELAYED_WORK(&tcp_ses->echo, cifs_echo_request);
 INIT_DELAYED_WORK(&tcp_ses->reconnect, smb2_reconnect_server);
 mutex_init(&tcp_ses->reconnect_mutex);
 memcpy(&tcp_ses->srcaddr, &ctx->srcaddr,
        sizeof(tcp_ses->srcaddr));
 memcpy(&tcp_ses->dstaddr, &ctx->dstaddr,
  sizeof(tcp_ses->dstaddr));
 if (ctx->use_client_guid)
  memcpy(tcp_ses->client_guid, ctx->client_guid,
         SMB2_CLIENT_GUID_SIZE);
 else
  generate_random_uuid(tcp_ses->client_guid);
 /*
 * at this point we are the only ones with the pointer
 * to the struct since the kernel thread not created yet
 * no need to spinlock this init of tcpStatus or srv_count
 */

 tcp_ses->tcpStatus = CifsNew;
 ++tcp_ses->srv_count;
 tcp_ses->echo_interval = ctx->echo_interval * HZ;

 if (tcp_ses->rdma) {
#ifndef CONFIG_CIFS_SMB_DIRECT
  cifs_dbg(VFS, "CONFIG_CIFS_SMB_DIRECT is not enabled\n");
  rc = -ENOENT;
  goto out_err_crypto_release;
#endif
  tcp_ses->smbd_conn = smbd_get_connection(
   tcp_ses, (struct sockaddr *)&ctx->dstaddr);
  if (tcp_ses->smbd_conn) {
   cifs_dbg(VFS, "RDMA transport established\n");
   rc = 0;
   goto smbd_connected;
  } else {
   rc = -ENOENT;
   goto out_err_crypto_release;
  }
 }
 rc = ip_connect(tcp_ses);
 if (rc < 0) {
  cifs_dbg(VFS, "Error connecting to socket. Aborting operation.\n");
  goto out_err_crypto_release;
 }
smbd_connected:
 /*
 * since we're in a cifs function already, we know that
 * this will succeed. No need for try_module_get().
 */

 __module_get(THIS_MODULE);
 tcp_ses->tsk = kthread_run(cifs_demultiplex_thread,
      tcp_ses, "cifsd");
 if (IS_ERR(tcp_ses->tsk)) {
  rc = PTR_ERR(tcp_ses->tsk);
  cifs_dbg(VFS, "error %d create cifsd thread\n", rc);
  module_put(THIS_MODULE);
  goto out_err_crypto_release;
 }
 tcp_ses->min_offload = ctx->min_offload;
 tcp_ses->retrans = ctx->retrans;
 /*
 * at this point we are the only ones with the pointer
 * to the struct since the kernel thread not created yet
 * no need to spinlock this update of tcpStatus
 */

 spin_lock(&tcp_ses->srv_lock);
 tcp_ses->tcpStatus = CifsNeedNegotiate;
 spin_unlock(&tcp_ses->srv_lock);

 if ((ctx->max_credits < 20) || (ctx->max_credits > 60000))
  tcp_ses->max_credits = SMB2_MAX_CREDITS_AVAILABLE;
 else
  tcp_ses->max_credits = ctx->max_credits;

 tcp_ses->nr_targets = 1;
 tcp_ses->ignore_signature = ctx->ignore_signature;
 /* thread spawned, put it on the list */
 spin_lock(&cifs_tcp_ses_lock);
 list_add(&tcp_ses->tcp_ses_list, &cifs_tcp_ses_list);
 spin_unlock(&cifs_tcp_ses_lock);

 /* queue echo request delayed work */
 queue_delayed_work(cifsiod_wq, &tcp_ses->echo, tcp_ses->echo_interval);

 return tcp_ses;

out_err_crypto_release:
 cifs_crypto_secmech_release(tcp_ses);

 put_net(cifs_net_ns(tcp_ses));

out_err:
 if (tcp_ses) {
  if (SERVER_IS_CHAN(tcp_ses))
   cifs_put_tcp_session(tcp_ses->primary_server, false);
  kfree(tcp_ses->hostname);
  kfree(tcp_ses->leaf_fullpath);
  if (tcp_ses->ssocket)
   sock_release(tcp_ses->ssocket);
  kfree(tcp_ses);
 }
 return ERR_PTR(rc);
}

/* this function must be called with ses_lock and chan_lock held */
static int match_session(struct cifs_ses *ses,
    struct smb3_fs_context *ctx,
    bool match_super)
{
 struct TCP_Server_Info *server = ses->server;
 enum securityEnum ctx_sec, ses_sec;

 if (!match_super && ctx->dfs_root_ses != ses->dfs_root_ses)
  return 0;

 /*
 * If an existing session is limited to less channels than
 * requested, it should not be reused
 */

 if (ses->chan_max < ctx->max_channels)
  return 0;

 ctx_sec = server->ops->select_sectype(server, ctx->sectype);
 ses_sec = server->ops->select_sectype(server, ses->sectype);

 if (ctx_sec != ses_sec)
  return 0;

 switch (ctx_sec) {
 case IAKerb:
 case Kerberos:
  if (!uid_eq(ctx->cred_uid, ses->cred_uid))
   return 0;
  break;
 case NTLMv2:
 case RawNTLMSSP:
 default:
  /* NULL username means anonymous session */
  if (ses->user_name == NULL) {
   if (!ctx->nullauth)
    return 0;
   break;
  }

  /* anything else takes username/password */
  if (strncmp(ses->user_name,
       ctx->username ? ctx->username : "",
       CIFS_MAX_USERNAME_LEN))
   return 0;
  if ((ctx->username && strlen(ctx->username) != 0) &&
      ses->password != NULL) {

   /* New mount can only share sessions with an existing mount if:
 * 1. Both password and password2 match, or
 * 2. password2 of the old mount matches password of the new mount
 *    and password of the old mount matches password2 of the new
 *   mount
 */

   if (ses->password2 != NULL && ctx->password2 != NULL) {
    if (!((strncmp(ses->password, ctx->password ?
     ctx->password : "", CIFS_MAX_PASSWORD_LEN) == 0 &&
     strncmp(ses->password2, ctx->password2,
     CIFS_MAX_PASSWORD_LEN) == 0) ||
     (strncmp(ses->password, ctx->password2,
     CIFS_MAX_PASSWORD_LEN) == 0 &&
     strncmp(ses->password2, ctx->password ?
     ctx->password : "", CIFS_MAX_PASSWORD_LEN) == 0)))
     return 0;

   } else if ((ses->password2 == NULL && ctx->password2 != NULL) ||
    (ses->password2 != NULL && ctx->password2 == NULL)) {
    return 0;

   } else {
    if (strncmp(ses->password, ctx->password ?
     ctx->password : "", CIFS_MAX_PASSWORD_LEN))
     return 0;
   }
  }
 }

 if (strcmp(ctx->local_nls->charset, ses->local_nls->charset))
  return 0;

 return 1;
}

/**
 * cifs_setup_ipc - helper to setup the IPC tcon for the session
 * @ses: smb session to issue the request on
 * @ctx: the superblock configuration context to use for building the
 *       new tree connection for the IPC (interprocess communication RPC)
 *
 * A new IPC connection is made and stored in the session
 * tcon_ipc. The IPC tcon has the same lifetime as the session.
 */

static int
cifs_setup_ipc(struct cifs_ses *ses, struct smb3_fs_context *ctx)
{
 int rc = 0, xid;
 struct cifs_tcon *tcon;
 char unc[SERVER_NAME_LENGTH + sizeof("//x/IPC$")] = {0};
 bool seal = false;
 struct TCP_Server_Info *server = ses->server;

 /*
 * If the mount request that resulted in the creation of the
 * session requires encryption, force IPC to be encrypted too.
 */

 if (ctx->seal) {
  if (server->capabilities & SMB2_GLOBAL_CAP_ENCRYPTION)
   seal = true;
  else {
   cifs_server_dbg(VFS,
     "IPC: server doesn't support encryption\n");
   return -EOPNOTSUPP;
  }
 }

 /* no need to setup directory caching on IPC share, so pass in false */
 tcon = tcon_info_alloc(false, netfs_trace_tcon_ref_new_ipc);
 if (tcon == NULL)
  return -ENOMEM;

 spin_lock(&server->srv_lock);
 scnprintf(unc, sizeof(unc), "\\\\%s\\IPC$", server->hostname);
 spin_unlock(&server->srv_lock);

 xid = get_xid();
 tcon->ses = ses;
 tcon->ipc = true;
 tcon->seal = seal;
 rc = server->ops->tree_connect(xid, ses, unc, tcon, ctx->local_nls);
 free_xid(xid);

 if (rc) {
  cifs_server_dbg(VFS, "failed to connect to IPC (rc=%d)\n", rc);
  tconInfoFree(tcon, netfs_trace_tcon_ref_free_ipc_fail);
  goto out;
 }

 cifs_dbg(FYI, "IPC tcon rc=%d ipc tid=0x%x\n", rc, tcon->tid);

 spin_lock(&tcon->tc_lock);
 tcon->status = TID_GOOD;
 spin_unlock(&tcon->tc_lock);
 ses->tcon_ipc = tcon;
out:
 return rc;
}

static struct cifs_ses *
cifs_find_smb_ses(struct TCP_Server_Info *server, struct smb3_fs_context *ctx)
{
 struct cifs_ses *ses, *ret = NULL;

 spin_lock(&cifs_tcp_ses_lock);
 list_for_each_entry(ses, &server->smb_ses_list, smb_ses_list) {
  spin_lock(&ses->ses_lock);
  if (ses->ses_status == SES_EXITING) {
   spin_unlock(&ses->ses_lock);
   continue;
  }
  spin_lock(&ses->chan_lock);
  if (match_session(ses, ctx, false)) {
   spin_unlock(&ses->chan_lock);
   spin_unlock(&ses->ses_lock);
   ret = ses;
   break;
  }
  spin_unlock(&ses->chan_lock);
  spin_unlock(&ses->ses_lock);
 }
 if (ret)
  cifs_smb_ses_inc_refcount(ret);
 spin_unlock(&cifs_tcp_ses_lock);
 return ret;
}

void __cifs_put_smb_ses(struct cifs_ses *ses)
{
 struct TCP_Server_Info *server = ses->server;
 struct cifs_tcon *tcon;
 unsigned int xid;
 size_t i;
 bool do_logoff;
 int rc;

 spin_lock(&cifs_tcp_ses_lock);
 spin_lock(&ses->ses_lock);
 cifs_dbg(FYI, "%s: id=0x%llx ses_count=%d ses_status=%u ipc=%s\n",
   __func__, ses->Suid, ses->ses_count, ses->ses_status,
   ses->tcon_ipc ? ses->tcon_ipc->tree_name : "none");
 if (ses->ses_status == SES_EXITING || --ses->ses_count > 0) {
  spin_unlock(&ses->ses_lock);
  spin_unlock(&cifs_tcp_ses_lock);
  return;
 }
 /* ses_count can never go negative */
 WARN_ON(ses->ses_count < 0);

 spin_lock(&ses->chan_lock);
 cifs_chan_clear_need_reconnect(ses, server);
 spin_unlock(&ses->chan_lock);

 do_logoff = ses->ses_status == SES_GOOD && server->ops->logoff;
 ses->ses_status = SES_EXITING;
 tcon = ses->tcon_ipc;
 ses->tcon_ipc = NULL;
 spin_unlock(&ses->ses_lock);
 spin_unlock(&cifs_tcp_ses_lock);

 /*
 * On session close, the IPC is closed and the server must release all
 * tcons of the session.  No need to send a tree disconnect here.
 *
 * Besides, it will make the server to not close durable and resilient
 * files on session close, as specified in MS-SMB2 3.3.5.6 Receiving an
 * SMB2 LOGOFF Request.
 */

 tconInfoFree(tcon, netfs_trace_tcon_ref_free_ipc);
 if (do_logoff) {
  xid = get_xid();
  rc = server->ops->logoff(xid, ses);
  cifs_server_dbg(FYI, "%s: Session Logoff: rc=%d\n",
    __func__, rc);
  _free_xid(xid);
 }

 spin_lock(&cifs_tcp_ses_lock);
 list_del_init(&ses->smb_ses_list);
 spin_unlock(&cifs_tcp_ses_lock);

 /* close any extra channels */
 for (i = 1; i < ses->chan_count; i++) {
  if (ses->chans[i].iface) {
   kref_put(&ses->chans[i].iface->refcount, release_iface);
   ses->chans[i].iface = NULL;
  }
  cifs_put_tcp_session(ses->chans[i].server, 0);
  ses->chans[i].server = NULL;
 }

 /* we now account for primary channel in iface->refcount */
 if (ses->chans[0].iface) {
  kref_put(&ses->chans[0].iface->refcount, release_iface);
  ses->chans[0].server = NULL;
 }

 sesInfoFree(ses);
 cifs_put_tcp_session(server, 0);
}

#ifdef CONFIG_KEYS

/* strlen("cifs:a:") + CIFS_MAX_DOMAINNAME_LEN + 1 */
#define CIFSCREDS_DESC_SIZE (7 + CIFS_MAX_DOMAINNAME_LEN + 1)

/* Populate username and pw fields from keyring if possible */
static int
cifs_set_cifscreds(struct smb3_fs_context *ctx, struct cifs_ses *ses)
{
 int rc = 0;
 int is_domain = 0;
 const char *delim, *payload;
 char *desc;
 ssize_t len;
 struct key *key;
 struct TCP_Server_Info *server = ses->server;
 struct sockaddr_in *sa;
 struct sockaddr_in6 *sa6;
 const struct user_key_payload *upayload;

 desc = kmalloc(CIFSCREDS_DESC_SIZE, GFP_KERNEL);
 if (!desc)
  return -ENOMEM;

 /* try to find an address key first */
 switch (server->dstaddr.ss_family) {
 case AF_INET:
  sa = (struct sockaddr_in *)&server->dstaddr;
  sprintf(desc, "cifs:a:%pI4", &sa->sin_addr.s_addr);
  break;
 case AF_INET6:
  sa6 = (struct sockaddr_in6 *)&server->dstaddr;
  sprintf(desc, "cifs:a:%pI6c", &sa6->sin6_addr.s6_addr);
  break;
 default:
  cifs_dbg(FYI, "Bad ss_family (%hu)\n",
    server->dstaddr.ss_family);
  rc = -EINVAL;
  goto out_err;
 }

 cifs_dbg(FYI, "%s: desc=%s\n", __func__, desc);
 key = request_key(&key_type_logon, desc, "");
 if (IS_ERR(key)) {
  if (!ses->domainName) {
   cifs_dbg(FYI, "domainName is NULL\n");
   rc = PTR_ERR(key);
   goto out_err;
  }

  /* didn't work, try to find a domain key */
  sprintf(desc, "cifs:d:%s", ses->domainName);
  cifs_dbg(FYI, "%s: desc=%s\n", __func__, desc);
  key = request_key(&key_type_logon, desc, "");
  if (IS_ERR(key)) {
   rc = PTR_ERR(key);
   goto out_err;
  }
  is_domain = 1;
 }

 down_read(&key->sem);
 upayload = user_key_payload_locked(key);
 if (IS_ERR_OR_NULL(upayload)) {
  rc = upayload ? PTR_ERR(upayload) : -EINVAL;
  goto out_key_put;
 }

 /* find first : in payload */
 payload = upayload->data;
 delim = strnchr(payload, upayload->datalen, ':');
 cifs_dbg(FYI, "payload=%s\n", payload);
 if (!delim) {
  cifs_dbg(FYI, "Unable to find ':' in payload (datalen=%d)\n",
    upayload->datalen);
  rc = -EINVAL;
  goto out_key_put;
 }

 len = delim - payload;
 if (len > CIFS_MAX_USERNAME_LEN || len <= 0) {
  cifs_dbg(FYI, "Bad value from username search (len=%zd)\n",
    len);
  rc = -EINVAL;
  goto out_key_put;
 }

 ctx->username = kstrndup(payload, len, GFP_KERNEL);
 if (!ctx->username) {
  cifs_dbg(FYI, "Unable to allocate %zd bytes for username\n",
    len);
  rc = -ENOMEM;
  goto out_key_put;
 }
 cifs_dbg(FYI, "%s: username=%s\n", __func__, ctx->username);

 len = key->datalen - (len + 1);
 if (len > CIFS_MAX_PASSWORD_LEN || len <= 0) {
  cifs_dbg(FYI, "Bad len for password search (len=%zd)\n", len);
  rc = -EINVAL;
  kfree(ctx->username);
  ctx->username = NULL;
  goto out_key_put;
 }

 ++delim;
 /* BB consider adding support for password2 (Key Rotation) for multiuser in future */
 ctx->password = kstrndup(delim, len, GFP_KERNEL);
 if (!ctx->password) {
  cifs_dbg(FYI, "Unable to allocate %zd bytes for password\n",
    len);
  rc = -ENOMEM;
  kfree(ctx->username);
  ctx->username = NULL;
  goto out_key_put;
 }

 /*
 * If we have a domain key then we must set the domainName in the
 * for the request.
 */

 if (is_domain && ses->domainName) {
  ctx->domainname = kstrdup(ses->domainName, GFP_KERNEL);
  if (!ctx->domainname) {
   cifs_dbg(FYI, "Unable to allocate %zd bytes for domain\n",
     len);
   rc = -ENOMEM;
   kfree(ctx->username);
   ctx->username = NULL;
   kfree_sensitive(ctx->password);
   /* no need to free ctx->password2 since not allocated in this path */
   ctx->password = NULL;
   goto out_key_put;
  }
 }

 strscpy(ctx->workstation_name, ses->workstation_name, sizeof(ctx->workstation_name));

out_key_put:
 up_read(&key->sem);
 key_put(key);
out_err:
 kfree(desc);
 cifs_dbg(FYI, "%s: returning %d\n", __func__, rc);
 return rc;
}
#else /* ! CONFIG_KEYS */
static inline int
cifs_set_cifscreds(struct smb3_fs_context *ctx __attribute__((unused)),
     struct cifs_ses *ses __attribute__((unused)))
{
 return -ENOSYS;
}
#endif /* CONFIG_KEYS */

/**
 * cifs_get_smb_ses - get a session matching @ctx data from @server
 * @server: server to setup the session to
 * @ctx: superblock configuration context to use to setup the session
 *
 * This function assumes it is being called from cifs_mount() where we
 * already got a server reference (server refcount +1). See
 * cifs_get_tcon() for refcount explanations.
 */

struct cifs_ses *
cifs_get_smb_ses(struct TCP_Server_Info *server, struct smb3_fs_context *ctx)
{
 struct sockaddr_in6 *addr6 = (struct sockaddr_in6 *)&server->dstaddr;
 struct sockaddr_in *addr = (struct sockaddr_in *)&server->dstaddr;
 struct cifs_ses *ses;
 unsigned int xid;
 int retries = 0;
 size_t len;
 int rc = 0;

 xid = get_xid();

 ses = cifs_find_smb_ses(server, ctx);
 if (ses) {
  cifs_dbg(FYI, "Existing smb sess found (status=%d)\n",
    ses->ses_status);

  spin_lock(&ses->chan_lock);
--> --------------------

--> maximum size reached

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

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

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