linux/fs/smb/client/connect.c

// SPDX-License-Identifier: LGPL-2.1
/*
 *
 *   Copyright (C) International Business Machines  Corp., 2002,2011
 *   Author(s): Steve French ([email protected])
 *
 */
#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
#define TLINK_IDLE_EXPIRE

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

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)
{}

static void smb2_query_server_interfaces(struct work_struct *work)
{}

/*
 * 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)
{}

/*
 * 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)
{}

static void
cifs_abort_connection(struct TCP_Server_Info *server)
{}

static bool cifs_tcp_ses_needs_reconnect(struct TCP_Server_Info *server, int num_targets)
{}

/*
 * 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)
{}

#ifdef CONFIG_CIFS_DFS_UPCALL
static int __reconnect_target_unlocked(struct TCP_Server_Info *server, const char *target)
{}

static int reconnect_target_unlocked(struct TCP_Server_Info *server, struct dfs_cache_tgt_list *tl,
				     struct dfs_cache_tgt_iterator **target_hint)
{}

static int reconnect_dfs_server(struct TCP_Server_Info *server)
{}

int cifs_reconnect(struct TCP_Server_Info *server, bool mark_smb_session)
{}
#else
int cifs_reconnect(struct TCP_Server_Info *server, bool mark_smb_session)
{
	return __cifs_reconnect(server, mark_smb_session);
}
#endif

static void
cifs_echo_request(struct work_struct *work)
{}

static bool
allocate_buffers(struct TCP_Server_Info *server)
{}

static bool
server_unresponsive(struct TCP_Server_Info *server)
{}

static inline bool
zero_credits(struct TCP_Server_Info *server)
{}

static int
cifs_readv_from_socket(struct TCP_Server_Info *server, struct msghdr *smb_msg)
{}

int
cifs_read_from_socket(struct TCP_Server_Info *server, char *buf,
		      unsigned int to_read)
{}

ssize_t
cifs_discard_from_socket(struct TCP_Server_Info *server, size_t to_read)
{}

int
cifs_read_page_from_socket(struct TCP_Server_Info *server, struct page *page,
	unsigned int page_offset, unsigned int to_read)
{}

int
cifs_read_iter_from_socket(struct TCP_Server_Info *server, struct iov_iter *iter,
			   unsigned int to_read)
{}

static bool
is_smb_response(struct TCP_Server_Info *server, unsigned char type)
{}

void
dequeue_mid(struct mid_q_entry *mid, bool malformed)
{}

static unsigned int
smb2_get_credits_from_hdr(char *buffer, struct TCP_Server_Info *server)
{}

static void
handle_mid(struct mid_q_entry *mid, struct TCP_Server_Info *server,
	   char *buf, int malformed)
{}

int
cifs_enable_signing(struct TCP_Server_Info *server, bool mnt_sign_required)
{}

static noinline_for_stack void
clean_demultiplex_info(struct TCP_Server_Info *server)
{}

static int
standard_receive3(struct TCP_Server_Info *server, struct mid_q_entry *mid)
{}

int
cifs_handle_standard(struct TCP_Server_Info *server, struct mid_q_entry *mid)
{}

static void
smb2_add_credits_from_hdr(char *buffer, struct TCP_Server_Info *server)
{}


static int
cifs_demultiplex_thread(void *p)
{}

int
cifs_ipaddr_cmp(struct sockaddr *srcaddr, struct sockaddr *rhs)
{}

/*
 * 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)
{}

/*
 * 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)
{}

static bool match_server_address(struct TCP_Server_Info *server, struct sockaddr *addr)
{}

static bool
match_security(struct TCP_Server_Info *server, struct smb3_fs_context *ctx)
{}

/* 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 TCP_Server_Info *
cifs_find_tcp_session(struct smb3_fs_context *ctx)
{}

void
cifs_put_tcp_session(struct TCP_Server_Info *server, int from_reconnect)
{}

struct TCP_Server_Info *
cifs_get_tcp_session(struct smb3_fs_context *ctx,
		     struct TCP_Server_Info *primary_server)
{}

/* 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)
{}

/**
 * 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)
{}

static struct cifs_ses *
cifs_find_smb_ses(struct TCP_Server_Info *server, struct smb3_fs_context *ctx)
{}

void __cifs_put_smb_ses(struct cifs_ses *ses)
{}

#ifdef CONFIG_KEYS

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

/* Populate username and pw fields from keyring if possible */
static int
cifs_set_cifscreds(struct smb3_fs_context *ctx, struct cifs_ses *ses)
{}
#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)
{}

/* this function must be called with tc_lock held */
static int match_tcon(struct cifs_tcon *tcon, struct smb3_fs_context *ctx)
{}

static struct cifs_tcon *
cifs_find_tcon(struct cifs_ses *ses, struct smb3_fs_context *ctx)
{}

void
cifs_put_tcon(struct cifs_tcon *tcon, enum smb3_tcon_ref_trace trace)
{}

/**
 * cifs_get_tcon - get a tcon matching @ctx data from @ses
 * @ses: smb session to issue the request on
 * @ctx: the superblock configuration context to use for building the
 *
 * - tcon refcount is the number of mount points using the tcon.
 * - ses refcount is the number of tcon using the session.
 *
 * 1. This function assumes it is being called from cifs_mount() where
 *    we already got a session reference (ses refcount +1).
 *
 * 2. Since we're in the context of adding a mount point, the end
 *    result should be either:
 *
 * a) a new tcon already allocated with refcount=1 (1 mount point) and
 *    its session refcount incremented (1 new tcon). This +1 was
 *    already done in (1).
 *
 * b) an existing tcon with refcount+1 (add a mount point to it) and
 *    identical ses refcount (no new tcon). Because of (1) we need to
 *    decrement the ses refcount.
 */
static struct cifs_tcon *
cifs_get_tcon(struct cifs_ses *ses, struct smb3_fs_context *ctx)
{}

void
cifs_put_tlink(struct tcon_link *tlink)
{}

static int
compare_mount_options(struct super_block *sb, struct cifs_mnt_data *mnt_data)
{}

static int match_prepath(struct super_block *sb,
			 struct cifs_tcon *tcon,
			 struct cifs_mnt_data *mnt_data)
{}

int
cifs_match_super(struct super_block *sb, void *data)
{}

#ifdef CONFIG_DEBUG_LOCK_ALLOC
static struct lock_class_key cifs_key[2];
static struct lock_class_key cifs_slock_key[2];

static inline void
cifs_reclassify_socket4(struct socket *sock)
{}

static inline void
cifs_reclassify_socket6(struct socket *sock)
{}
#else
static inline void
cifs_reclassify_socket4(struct socket *sock)
{
}

static inline void
cifs_reclassify_socket6(struct socket *sock)
{
}
#endif

/* See RFC1001 section 14 on representation of Netbios names */
static void rfc1002mangle(char *target, char *source, unsigned int length)
{}

static int
bind_socket(struct TCP_Server_Info *server)
{}

static int
ip_rfc1001_connect(struct TCP_Server_Info *server)
{}

static int
generic_ip_connect(struct TCP_Server_Info *server)
{}

static int
ip_connect(struct TCP_Server_Info *server)
{}

#ifdef CONFIG_CIFS_ALLOW_INSECURE_LEGACY
void reset_cifs_unix_caps(unsigned int xid, struct cifs_tcon *tcon,
			  struct cifs_sb_info *cifs_sb, struct smb3_fs_context *ctx)
{}
#endif /* CONFIG_CIFS_ALLOW_INSECURE_LEGACY */

int cifs_setup_cifs_sb(struct cifs_sb_info *cifs_sb)
{}

/* Release all succeed connections */
void cifs_mount_put_conns(struct cifs_mount_ctx *mnt_ctx)
{}

int cifs_mount_get_session(struct cifs_mount_ctx *mnt_ctx)
{}

int cifs_mount_get_tcon(struct cifs_mount_ctx *mnt_ctx)
{}

static int mount_setup_tlink(struct cifs_sb_info *cifs_sb, struct cifs_ses *ses,
			     struct cifs_tcon *tcon)
{}

static int
cifs_are_all_path_components_accessible(struct TCP_Server_Info *server,
					unsigned int xid,
					struct cifs_tcon *tcon,
					struct cifs_sb_info *cifs_sb,
					char *full_path,
					int added_treename)
{}

/*
 * Check if path is remote (i.e. a DFS share).
 *
 * Return -EREMOTE if it is, otherwise 0 or -errno.
 */
int cifs_is_path_remote(struct cifs_mount_ctx *mnt_ctx)
{}

#ifdef CONFIG_CIFS_DFS_UPCALL
int cifs_mount(struct cifs_sb_info *cifs_sb, struct smb3_fs_context *ctx)
{}
#else
int cifs_mount(struct cifs_sb_info *cifs_sb, struct smb3_fs_context *ctx)
{
	int rc = 0;
	struct cifs_mount_ctx mnt_ctx = { .cifs_sb = cifs_sb, .fs_ctx = ctx, };

	rc = cifs_mount_get_session(&mnt_ctx);
	if (rc)
		goto error;

	rc = cifs_mount_get_tcon(&mnt_ctx);
	if (!rc) {
		/*
		 * Prevent superblock from being created with any missing
		 * connections.
		 */
		if (WARN_ON(!mnt_ctx.server))
			rc = -EHOSTDOWN;
		else if (WARN_ON(!mnt_ctx.ses))
			rc = -EACCES;
		else if (WARN_ON(!mnt_ctx.tcon))
			rc = -ENOENT;
	}
	if (rc)
		goto error;

	rc = cifs_is_path_remote(&mnt_ctx);
	if (rc == -EREMOTE)
		rc = -EOPNOTSUPP;
	if (rc)
		goto error;

	rc = mount_setup_tlink(cifs_sb, mnt_ctx.ses, mnt_ctx.tcon);
	if (rc)
		goto error;

	free_xid(mnt_ctx.xid);
	return rc;

error:
	cifs_mount_put_conns(&mnt_ctx);
	return rc;
}
#endif

#ifdef CONFIG_CIFS_ALLOW_INSECURE_LEGACY
/*
 * Issue a TREE_CONNECT request.
 */
int
CIFSTCon(const unsigned int xid, struct cifs_ses *ses,
	 const char *tree, struct cifs_tcon *tcon,
	 const struct nls_table *nls_codepage)
{}
#endif /* CONFIG_CIFS_ALLOW_INSECURE_LEGACY */

static void delayed_free(struct rcu_head *p)
{}

void
cifs_umount(struct cifs_sb_info *cifs_sb)
{}

int
cifs_negotiate_protocol(const unsigned int xid, struct cifs_ses *ses,
			struct TCP_Server_Info *server)
{}

int
cifs_setup_session(const unsigned int xid, struct cifs_ses *ses,
		   struct TCP_Server_Info *server,
		   struct nls_table *nls_info)
{}

static int
cifs_set_vol_auth(struct smb3_fs_context *ctx, struct cifs_ses *ses)
{}

static struct cifs_tcon *
__cifs_construct_tcon(struct cifs_sb_info *cifs_sb, kuid_t fsuid)
{}

static struct cifs_tcon *
cifs_construct_tcon(struct cifs_sb_info *cifs_sb, kuid_t fsuid)
{}

struct cifs_tcon *
cifs_sb_master_tcon(struct cifs_sb_info *cifs_sb)
{}

/* find and return a tlink with given uid */
static struct tcon_link *
tlink_rb_search(struct rb_root *root, kuid_t uid)
{}

/* insert a tcon_link into the tree */
static void
tlink_rb_insert(struct rb_root *root, struct tcon_link *new_tlink)
{}

/*
 * Find or construct an appropriate tcon given a cifs_sb and the fsuid of the
 * current task.
 *
 * If the superblock doesn't refer to a multiuser mount, then just return
 * the master tcon for the mount.
 *
 * First, search the rbtree for an existing tcon for this fsuid. If one
 * exists, then check to see if it's pending construction. If it is then wait
 * for construction to complete. Once it's no longer pending, check to see if
 * it failed and either return an error or retry construction, depending on
 * the timeout.
 *
 * If one doesn't exist then insert a new tcon_link struct into the tree and
 * try to construct a new one.
 */
struct tcon_link *
cifs_sb_tlink(struct cifs_sb_info *cifs_sb)
{}

/*
 * periodic workqueue job that scans tcon_tree for a superblock and closes
 * out tcons.
 */
static void
cifs_prune_tlinks(struct work_struct *work)
{}

#ifndef CONFIG_CIFS_DFS_UPCALL
int cifs_tree_connect(const unsigned int xid, struct cifs_tcon *tcon, const struct nls_table *nlsc)
{
	int rc;
	const struct smb_version_operations *ops = tcon->ses->server->ops;

	/* only send once per connect */
	spin_lock(&tcon->tc_lock);

	/* if tcon is marked for needing reconnect, update state */
	if (tcon->need_reconnect)
		tcon->status = TID_NEED_TCON;

	if (tcon->status == TID_GOOD) {
		spin_unlock(&tcon->tc_lock);
		return 0;
	}

	if (tcon->status != TID_NEW &&
	    tcon->status != TID_NEED_TCON) {
		spin_unlock(&tcon->tc_lock);
		return -EHOSTDOWN;
	}

	tcon->status = TID_IN_TCON;
	spin_unlock(&tcon->tc_lock);

	rc = ops->tree_connect(xid, tcon->ses, tcon->tree_name, tcon, nlsc);
	if (rc) {
		spin_lock(&tcon->tc_lock);
		if (tcon->status == TID_IN_TCON)
			tcon->status = TID_NEED_TCON;
		spin_unlock(&tcon->tc_lock);
	} else {
		spin_lock(&tcon->tc_lock);
		if (tcon->status == TID_IN_TCON)
			tcon->status = TID_GOOD;
		tcon->need_reconnect = false;
		spin_unlock(&tcon->tc_lock);
	}

	return rc;
}
#endif