| // 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/fs_context.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/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 "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); | |
| static struct mchan_mount *mchan_mount_alloc(struct cifs_ses *ses); | |
| static void mchan_mount_free(struct mchan_mount *mchan_mount); | |
| static void mchan_mount_work_fn(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; | |
| if (!list_empty(&nserver->rlist)) | |
| continue; | |
| nserver->srv_count++; | |
| list_add(&nserver->rlist, &reco); | |
| } | |
| spin_unlock(&ses->chan_lock); | |
| spin_unlock(&ses->ses_lock); | |
| } | |
| } | |
| spin_lock(&cifs_tcp_ses_lock); | |
| list_for_each_entry_safe(server, nserver, &reco, rlist) { | |
| list_del_init(&server->rlist); | |
| set_need_reco(server); | |
| spin_unlock(&cifs_tcp_ses_lock); | |
| cifs_put_tcp_session(server, 0); | |
| spin_lock(&cifs_tcp_ses_lock); | |
| } | |
| spin_unlock(&cifs_tcp_ses_lock); | |
| } | |
| /* | |
| * 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; | |
| } else if (cifs_rdma_enabled(server)) { | |
| smbd_destroy(server); | |
| } | |
| 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) { | |
| smb_get_mid(mid); | |
| 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(server, mid); | |
| release_mid(server, mid); | |
| } | |
| } | |
| 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); | |
| cifs_queue_server_reconn(server); | |
| } | |
| } 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: %pe\n", | |
| __func__, 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); | |
| cifs_queue_server_reconn(server); | |
| } 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, true, true); | |
| } | |
| 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) { | |
| smb_get_mid(mid); | |
| 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(server, mid); | |
| release_mid(server, 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 TCP_Server_Info *server, struct mid_q_entry *mid, bool malformed) | |
| { | |
| #ifdef CONFIG_CIFS_STATS2 | |
| mid->when_received = jiffies; | |
| #endif | |
| spin_lock(&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(&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(&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(server, 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); | |
| cancel_delayed_work_sync(&server->reconnect); | |
| 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); | |
| smb_get_mid(mid_entry); | |
| 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(server, mid_entry); | |
| release_mid(server, 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_sensitive(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)) { | |
| 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) { | |
| 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->pdu_size, | |
| 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; | |
| server->total_read = 0; | |
| /* | |
| * The right amount was read from socket - 4 bytes, | |
| * so we can now interpret the length field. | |
| */ | |
| pdu_length = be32_to_cpup(((__be32 *)buf)) & 0xffffff; | |
| 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, | |
| 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]->response_pdu_len = pdu_length; | |
| 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(server, 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(server, mids[i]); | |
| release_mid(server, 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], pdu_length, | |
| 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_obj(struct TCP_Server_Info); | |
| 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_LIST_HEAD(&tcp_ses->rlist); | |
| 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->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 | |
| */ | |
| tcp_ses->tcpStatus = CifsNeedNegotiate; | |
| 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; | |
| tcp_ses->tsk = kthread_create(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; | |
| } | |
| /* thread created, 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); | |
| /* | |
| * Use split create/wake logic to ensure that tcp_ses is fully populated | |
| * and tcp_ses->tsk is valid | |
| */ | |
| wake_up_process(tcp_ses->tsk); | |
| 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); | |
| smbd_destroy(tcp_ses); | |
| 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; | |
| if (strncmp(ses->user_name ?: "", | |
| ctx->username ?: "", | |
| CIFS_MAX_USERNAME_LEN)) | |
| 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 | |
| * @seal: if encryption is requested | |
| * | |
| * A new IPC connection is made and stored in the session | |
| * tcon_ipc. The IPC tcon has the same lifetime as the session. | |
| */ | |
| struct cifs_tcon *cifs_setup_ipc(struct cifs_ses *ses, bool seal) | |
| { | |
| int rc = 0, xid; | |
| struct cifs_tcon *tcon; | |
| char unc[SERVER_NAME_LENGTH + sizeof("//x/IPC$")] = {0}; | |
| 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 (seal && !(server->capabilities & SMB2_GLOBAL_CAP_ENCRYPTION)) { | |
| cifs_server_dbg(VFS, "IPC: server doesn't support encryption\n"); | |
| return ERR_PTR(-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 ERR_PTR(-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, ses->local_nls); | |
| free_xid(xid); | |
| if (rc) { | |
| cifs_server_dbg(VFS | ONCE, "failed to connect to IPC (rc=%d)\n", rc); | |
| tconInfoFree(tcon, netfs_trace_tcon_ref_free_ipc_fail); | |
| return ERR_PTR(rc); | |
| } | |
| 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); | |
| return tcon; | |
| } | |
| 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 | |
| /* 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; | |
| size_t desc_sz; | |
| 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; | |
| /* "cifs:a:" and "cifs:d:" are the same length; +1 for NUL terminator */ | |
| desc_sz = strlen("cifs:a:") + CIFS_MAX_DOMAINNAME_LEN + 1; | |
| desc = kmalloc(desc_sz, 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; | |
| snprintf(desc, desc_sz, "cifs:a:%pI4", &sa->sin_addr.s_addr); | |
| break; | |
| case AF_INET6: | |
| sa6 = (struct sockaddr_in6 *)&server->dstaddr; | |
| snprintf(desc, desc_sz, "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 */ | |
| snprintf(desc, desc_sz, "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, ':'); | |
| 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 __maybe_unused, | |
| struct cifs_ses *ses __maybe_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_tcon *ipc; | |
| 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); | |
| if (cifs_chan_needs_reconnect(ses, server)) { | |
| spin_unlock(&ses->chan_lock); | |
| cifs_dbg(FYI, "Session needs reconnect\n"); | |
| mutex_lock(&ses->session_mutex); | |
| retry_old_session: | |
| rc = cifs_negotiate_protocol(xid, ses, server); | |
| if (rc) { | |
| mutex_unlock(&ses->session_mutex); | |
| /* problem -- put our ses reference */ | |
| cifs_put_smb_ses(ses); | |
| free_xid(xid); | |
| return ERR_PTR(rc); | |
| } | |
| rc = cifs_setup_session(xid, ses, server, | |
| ctx->local_nls); | |
| if (rc) { | |
| if (((rc == -EACCES) || (rc == -EKEYEXPIRED) || | |
| (rc == -EKEYREVOKED)) && !retries && ses->password2) { | |
| retries++; | |
| cifs_dbg(FYI, "Session reconnect failed, retrying with alternate password\n"); | |
| swap(ses->password, ses->password2); | |
| goto retry_old_session; | |
| } | |
| mutex_unlock(&ses->session_mutex); | |
| /* problem -- put our reference */ | |
| cifs_put_smb_ses(ses); | |
| free_xid(xid); | |
| return ERR_PTR(rc); | |
| } | |
| mutex_unlock(&ses->session_mutex); | |
| spin_lock(&ses->chan_lock); | |
| } | |
| spin_unlock(&ses->chan_lock); | |
| /* existing SMB ses has a server reference already */ | |
| cifs_put_tcp_session(server, 0); | |
| free_xid(xid); | |
| return ses; | |
| } | |
| rc = -ENOMEM; | |
| cifs_dbg(FYI, "Existing smb sess not found\n"); | |
| ses = sesInfoAlloc(); | |
| if (ses == NULL) | |
| goto get_ses_fail; | |
| /* new SMB session uses our server ref */ | |
| ses->server = server; | |
| if (server->dstaddr.ss_family == AF_INET6) | |
| sprintf(ses->ip_addr, "%pI6", &addr6->sin6_addr); | |
| else | |
| sprintf(ses->ip_addr, "%pI4", &addr->sin_addr); | |
| if (ctx->username) { | |
| ses->user_name = kstrdup(ctx->username, GFP_KERNEL); | |
| if (!ses->user_name) | |
| goto get_ses_fail; | |
| } | |
| /* ctx->password freed at unmount */ | |
| if (ctx->password) { | |
| ses->password = kstrdup(ctx->password, GFP_KERNEL); | |
| if (!ses->password) | |
| goto get_ses_fail; | |
| } | |
| /* ctx->password freed at unmount */ | |
| if (ctx->password2) { | |
| ses->password2 = kstrdup(ctx->password2, GFP_KERNEL); | |
| if (!ses->password2) | |
| goto get_ses_fail; | |
| } | |
| if (ctx->domainname) { | |
| ses->domainName = kstrdup(ctx->domainname, GFP_KERNEL); | |
| if (!ses->domainName) | |
| goto get_ses_fail; | |
| len = strnlen(ctx->domainname, CIFS_MAX_DOMAINNAME_LEN); | |
| if (!cifs_netbios_name(ctx->domainname, len)) { | |
| ses->dns_dom = kstrndup(ctx->domainname, | |
| len, GFP_KERNEL); | |
| if (!ses->dns_dom) | |
| goto get_ses_fail; | |
| } | |
| } | |
| strscpy(ses->workstation_name, ctx->workstation_name, sizeof(ses->workstation_name)); | |
| if (ctx->domainauto) | |
| ses->domainAuto = ctx->domainauto; | |
| ses->cred_uid = ctx->cred_uid; | |
| ses->linux_uid = ctx->linux_uid; | |
| ses->unicode = ctx->unicode; | |
| ses->sectype = ctx->sectype; | |
| ses->sign = ctx->sign; | |
| /* | |
| *Explicitly marking upcall_target mount option for easier handling | |
| * by cifs_spnego.c and eventually cifs.upcall.c | |
| */ | |
| switch (ctx->upcall_target) { | |
| case UPTARGET_UNSPECIFIED: /* default to app */ | |
| case UPTARGET_APP: | |
| ses->upcall_target = UPTARGET_APP; | |
| break; | |
| case UPTARGET_MOUNT: | |
| ses->upcall_target = UPTARGET_MOUNT; | |
| break; | |
| default: | |
| // should never happen | |
| ses->upcall_target = UPTARGET_APP; | |
| break; | |
| } | |
| ses->local_nls = load_nls(ctx->local_nls->charset); | |
| /* add server as first channel */ | |
| spin_lock(&ses->chan_lock); | |
| ses->chans[0].server = server; | |
| ses->chan_count = 1; | |
| ses->chan_max = ctx->multichannel ? ctx->max_channels:1; | |
| ses->chans_need_reconnect = 1; | |
| spin_unlock(&ses->chan_lock); | |
| retry_new_session: | |
| mutex_lock(&ses->session_mutex); | |
| rc = cifs_negotiate_protocol(xid, ses, server); | |
| if (!rc) | |
| rc = cifs_setup_session(xid, ses, server, ctx->local_nls); | |
| mutex_unlock(&ses->session_mutex); | |
| /* each channel uses a different signing key */ | |
| spin_lock(&ses->chan_lock); | |
| memcpy(ses->chans[0].signkey, ses->smb3signingkey, | |
| sizeof(ses->smb3signingkey)); | |
| spin_unlock(&ses->chan_lock); | |
| if (rc) { | |
| if (((rc == -EACCES) || (rc == -EKEYEXPIRED) || | |
| (rc == -EKEYREVOKED)) && !retries && ses->password2) { | |
| retries++; | |
| cifs_dbg(FYI, "Session setup failed, retrying with alternate password\n"); | |
| swap(ses->password, ses->password2); | |
| goto retry_new_session; | |
| } else | |
| goto get_ses_fail; | |
| } | |
| /* | |
| * success, put it on the list and add it as first channel | |
| * note: the session becomes active soon after this. So you'll | |
| * need to lock before changing something in the session. | |
| */ | |
| spin_lock(&cifs_tcp_ses_lock); | |
| ses->dfs_root_ses = ctx->dfs_root_ses; | |
| list_add(&ses->smb_ses_list, &server->smb_ses_list); | |
| spin_unlock(&cifs_tcp_ses_lock); | |
| ipc = cifs_setup_ipc(ses, ctx->seal); | |
| spin_lock(&cifs_tcp_ses_lock); | |
| spin_lock(&ses->ses_lock); | |
| ses->tcon_ipc = !IS_ERR(ipc) ? ipc : NULL; | |
| spin_unlock(&ses->ses_lock); | |
| spin_unlock(&cifs_tcp_ses_lock); | |
| free_xid(xid); | |
| return ses; | |
| get_ses_fail: | |
| sesInfoFree(ses); | |
| free_xid(xid); | |
| return ERR_PTR(rc); | |
| } | |
| /* this function must be called with tc_lock held */ | |
| static int match_tcon(struct cifs_tcon *tcon, struct smb3_fs_context *ctx) | |
| { | |
| struct TCP_Server_Info *server = tcon->ses->server; | |
| if (tcon->status == TID_EXITING) | |
| return 0; | |
| if (tcon->origin_fullpath) { | |
| if (!ctx->source || | |
| !dfs_src_pathname_equal(ctx->source, | |
| tcon->origin_fullpath)) | |
| return 0; | |
| } else if (!server->leaf_fullpath && | |
| strncmp(tcon->tree_name, ctx->UNC, MAX_TREE_SIZE)) { | |
| return 0; | |
| } | |
| if (tcon->seal != ctx->seal) | |
| return 0; | |
| if (tcon->snapshot_time != ctx->snapshot_time) | |
| return 0; | |
| if (tcon->handle_timeout != ctx->handle_timeout) | |
| return 0; | |
| if (tcon->no_lease != ctx->no_lease) | |
| return 0; | |
| if (tcon->nodelete != ctx->nodelete) | |
| return 0; | |
| if (tcon->posix_extensions != ctx->linux_ext) | |
| return 0; | |
| return 1; | |
| } | |
| static struct cifs_tcon * | |
| cifs_find_tcon(struct cifs_ses *ses, struct smb3_fs_context *ctx) | |
| { | |
| struct cifs_tcon *tcon; | |
| spin_lock(&cifs_tcp_ses_lock); | |
| list_for_each_entry(tcon, &ses->tcon_list, tcon_list) { | |
| spin_lock(&tcon->tc_lock); | |
| if (!match_tcon(tcon, ctx)) { | |
| spin_unlock(&tcon->tc_lock); | |
| continue; | |
| } | |
| ++tcon->tc_count; | |
| trace_smb3_tcon_ref(tcon->debug_id, tcon->tc_count, | |
| netfs_trace_tcon_ref_get_find); | |
| spin_unlock(&tcon->tc_lock); | |
| spin_unlock(&cifs_tcp_ses_lock); | |
| return tcon; | |
| } | |
| spin_unlock(&cifs_tcp_ses_lock); | |
| return NULL; | |
| } | |
| void | |
| cifs_put_tcon(struct cifs_tcon *tcon, enum smb3_tcon_ref_trace trace) | |
| { | |
| unsigned int xid; | |
| struct cifs_ses *ses; | |
| LIST_HEAD(ses_list); | |
| /* | |
| * IPC tcon share the lifetime of their session and are | |
| * destroyed in the session put function | |
| */ | |
| if (tcon == NULL || tcon->ipc) | |
| return; | |
| ses = tcon->ses; | |
| cifs_dbg(FYI, "%s: tc_count=%d\n", __func__, tcon->tc_count); | |
| spin_lock(&cifs_tcp_ses_lock); | |
| spin_lock(&tcon->tc_lock); | |
| trace_smb3_tcon_ref(tcon->debug_id, tcon->tc_count - 1, trace); | |
| if (--tcon->tc_count > 0) { | |
| spin_unlock(&tcon->tc_lock); | |
| spin_unlock(&cifs_tcp_ses_lock); | |
| return; | |
| } | |
| /* tc_count can never go negative */ | |
| WARN_ON(tcon->tc_count < 0); | |
| list_del_init(&tcon->tcon_list); | |
| tcon->status = TID_EXITING; | |
| spin_unlock(&tcon->tc_lock); | |
| spin_unlock(&cifs_tcp_ses_lock); | |
| /* cancel polling of interfaces */ | |
| cancel_delayed_work_sync(&tcon->query_interfaces); | |
| #ifdef CONFIG_CIFS_DFS_UPCALL | |
| cancel_delayed_work_sync(&tcon->dfs_cache_work); | |
| list_replace_init(&tcon->dfs_ses_list, &ses_list); | |
| #endif | |
| if (tcon->use_witness) { | |
| int rc; | |
| rc = cifs_swn_unregister(tcon); | |
| if (rc < 0) { | |
| cifs_dbg(VFS, "%s: Failed to unregister for witness notifications: %d\n", | |
| __func__, rc); | |
| } | |
| } | |
| xid = get_xid(); | |
| if (ses->server->ops->tree_disconnect) | |
| ses->server->ops->tree_disconnect(xid, tcon); | |
| _free_xid(xid); | |
| cifs_fscache_release_super_cookie(tcon); | |
| tconInfoFree(tcon, netfs_trace_tcon_ref_free); | |
| cifs_put_smb_ses(ses); | |
| #ifdef CONFIG_CIFS_DFS_UPCALL | |
| dfs_put_root_smb_sessions(&ses_list); | |
| #endif | |
| } | |
| /** | |
| * 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) | |
| { | |
| struct cifs_tcon *tcon; | |
| bool nohandlecache; | |
| int rc, xid; | |
| tcon = cifs_find_tcon(ses, ctx); | |
| if (tcon) { | |
| /* | |
| * tcon has refcount already incremented but we need to | |
| * decrement extra ses reference gotten by caller (case b) | |
| */ | |
| cifs_dbg(FYI, "Found match on UNC path\n"); | |
| cifs_put_smb_ses(ses); | |
| return tcon; | |
| } | |
| if (!ses->server->ops->tree_connect) { | |
| rc = -ENOSYS; | |
| goto out_fail; | |
| } | |
| if (ses->server->dialect >= SMB20_PROT_ID && | |
| (ses->server->capabilities & SMB2_GLOBAL_CAP_DIRECTORY_LEASING)) | |
| nohandlecache = ctx->nohandlecache || !dir_cache_timeout; | |
| else | |
| nohandlecache = true; | |
| tcon = tcon_info_alloc(!nohandlecache, netfs_trace_tcon_ref_new); | |
| if (tcon == NULL) { | |
| rc = -ENOMEM; | |
| goto out_fail; | |
| } | |
| tcon->nohandlecache = nohandlecache; | |
| if (ctx->snapshot_time) { | |
| if (ses->server->vals->protocol_id == 0) { | |
| cifs_dbg(VFS, | |
| "Use SMB2 or later for snapshot mount option\n"); | |
| rc = -EOPNOTSUPP; | |
| goto out_fail; | |
| } else | |
| tcon->snapshot_time = ctx->snapshot_time; | |
| } | |
| if (ctx->handle_timeout) { | |
| if (ses->server->vals->protocol_id == 0) { | |
| cifs_dbg(VFS, | |
| "Use SMB2.1 or later for handle timeout option\n"); | |
| rc = -EOPNOTSUPP; | |
| goto out_fail; | |
| } else | |
| tcon->handle_timeout = ctx->handle_timeout; | |
| } | |
| tcon->ses = ses; | |
| if (ctx->password) { | |
| tcon->password = kstrdup(ctx->password, GFP_KERNEL); | |
| if (!tcon->password) { | |
| rc = -ENOMEM; | |
| goto out_fail; | |
| } | |
| } | |
| if (ctx->seal) { | |
| if (ses->server->vals->protocol_id == 0) { | |
| cifs_dbg(VFS, | |
| "SMB3 or later required for encryption\n"); | |
| rc = -EOPNOTSUPP; | |
| goto out_fail; | |
| } else if (tcon->ses->server->capabilities & | |
| SMB2_GLOBAL_CAP_ENCRYPTION) | |
| tcon->seal = true; | |
| else { | |
| cifs_dbg(VFS, "Encryption is not supported on share\n"); | |
| rc = -EOPNOTSUPP; | |
| goto out_fail; | |
| } | |
| } | |
| if (ctx->linux_ext) { | |
| if (ses->server->posix_ext_supported) { | |
| tcon->posix_extensions = true; | |
| pr_warn_once("SMB3.11 POSIX Extensions are experimental\n"); | |
| } else if ((ses->server->vals->protocol_id == SMB311_PROT_ID) || | |
| (strcmp(ses->server->vals->version_string, | |
| SMB3ANY_VERSION_STRING) == 0) || | |
| (strcmp(ses->server->vals->version_string, | |
| SMBDEFAULT_VERSION_STRING) == 0)) { | |
| cifs_dbg(VFS, "Server does not support mounting with posix SMB3.11 extensions\n"); | |
| rc = -EOPNOTSUPP; | |
| goto out_fail; | |
| } else if (ses->server->vals->protocol_id == SMB10_PROT_ID) | |
| if (cap_unix(ses)) | |
| cifs_dbg(FYI, "Unix Extensions requested on SMB1 mount\n"); | |
| else { | |
| cifs_dbg(VFS, "SMB1 Unix Extensions not supported by server\n"); | |
| rc = -EOPNOTSUPP; | |
| goto out_fail; | |
| } else { | |
| cifs_dbg(VFS, | |
| "Check vers= mount option. SMB3.11 disabled but required for POSIX extensions\n"); | |
| rc = -EOPNOTSUPP; | |
| goto out_fail; | |
| } | |
| } | |
| xid = get_xid(); | |
| rc = ses->server->ops->tree_connect(xid, ses, ctx->UNC, tcon, | |
| ctx->local_nls); | |
| free_xid(xid); | |
| cifs_dbg(FYI, "Tcon rc = %d\n", rc); | |
| if (rc) | |
| goto out_fail; | |
| tcon->use_persistent = false; | |
| /* check if SMB2 or later, CIFS does not support persistent handles */ | |
| if (ctx->persistent) { | |
| if (ses->server->vals->protocol_id == 0) { | |
| cifs_dbg(VFS, | |
| "SMB3 or later required for persistent handles\n"); | |
| rc = -EOPNOTSUPP; | |
| goto out_fail; | |
| } else if (ses->server->capabilities & | |
| SMB2_GLOBAL_CAP_PERSISTENT_HANDLES) | |
| tcon->use_persistent = true; | |
| else /* persistent handles requested but not supported */ { | |
| cifs_dbg(VFS, | |
| "Persistent handles not supported on share\n"); | |
| rc = -EOPNOTSUPP; | |
| goto out_fail; | |
| } | |
| } else if ((tcon->capabilities & SMB2_SHARE_CAP_CONTINUOUS_AVAILABILITY) | |
| && (ses->server->capabilities & SMB2_GLOBAL_CAP_PERSISTENT_HANDLES) | |
| && (ctx->nopersistent == false)) { | |
| cifs_dbg(FYI, "enabling persistent handles\n"); | |
| tcon->use_persistent = true; | |
| } else if (ctx->resilient) { | |
| if (ses->server->vals->protocol_id == 0) { | |
| cifs_dbg(VFS, | |
| "SMB2.1 or later required for resilient handles\n"); | |
| rc = -EOPNOTSUPP; | |
| goto out_fail; | |
| } | |
| tcon->use_resilient = true; | |
| } | |
| tcon->use_witness = false; | |
| if (IS_ENABLED(CONFIG_CIFS_SWN_UPCALL) && ctx->witness) { | |
| if (ses->server->vals->protocol_id >= SMB30_PROT_ID) { | |
| if (tcon->capabilities & SMB2_SHARE_CAP_CLUSTER) { | |
| /* | |
| * Set witness in use flag in first place | |
| * to retry registration in the echo task | |
| */ | |
| tcon->use_witness = true; | |
| /* And try to register immediately */ | |
| rc = cifs_swn_register(tcon); | |
| if (rc < 0) { | |
| cifs_dbg(VFS, "Failed to register for witness notifications: %d\n", rc); | |
| goto out_fail; | |
| } | |
| } else { | |
| /* TODO: try to extend for non-cluster uses (eg multichannel) */ | |
| cifs_dbg(VFS, "witness requested on mount but no CLUSTER capability on share\n"); | |
| rc = -EOPNOTSUPP; | |
| goto out_fail; | |
| } | |
| } else { | |
| cifs_dbg(VFS, "SMB3 or later required for witness option\n"); | |
| rc = -EOPNOTSUPP; | |
| goto out_fail; | |
| } | |
| } | |
| /* If the user really knows what they are doing they can override */ | |
| if (tcon->share_flags & SMB2_SHAREFLAG_NO_CACHING) { | |
| if (ctx->cache_ro) | |
| cifs_dbg(VFS, "cache=ro requested on mount but NO_CACHING flag set on share\n"); | |
| else if (ctx->cache_rw) | |
| cifs_dbg(VFS, "cache=singleclient requested on mount but NO_CACHING flag set on share\n"); | |
| } | |
| if (ctx->no_lease) { | |
| if (ses->server->vals->protocol_id == 0) { | |
| cifs_dbg(VFS, | |
| "SMB2 or later required for nolease option\n"); | |
| rc = -EOPNOTSUPP; | |
| goto out_fail; | |
| } else | |
| tcon->no_lease = ctx->no_lease; | |
| } | |
| /* | |
| * We can have only one retry value for a connection to a share so for | |
| * resources mounted more than once to the same server share the last | |
| * value passed in for the retry flag is used. | |
| */ | |
| tcon->retry = ctx->retry; | |
| tcon->nocase = ctx->nocase; | |
| tcon->broken_sparse_sup = ctx->no_sparse; | |
| tcon->max_cached_dirs = ctx->max_cached_dirs; | |
| tcon->nodelete = ctx->nodelete; | |
| tcon->local_lease = ctx->local_lease; | |
| tcon->status = TID_GOOD; | |
| if (ses->server->dialect >= SMB30_PROT_ID && | |
| (ses->server->capabilities & SMB2_GLOBAL_CAP_MULTI_CHANNEL)) { | |
| /* schedule query interfaces poll */ | |
| queue_delayed_work(cifsiod_wq, &tcon->query_interfaces, | |
| (SMB_INTERFACE_POLL_INTERVAL * HZ)); | |
| } | |
| spin_lock(&cifs_tcp_ses_lock); | |
| list_add(&tcon->tcon_list, &ses->tcon_list); | |
| spin_unlock(&cifs_tcp_ses_lock); | |
| return tcon; | |
| out_fail: | |
| tconInfoFree(tcon, netfs_trace_tcon_ref_free_fail); | |
| return ERR_PTR(rc); | |
| } | |
| void | |
| cifs_put_tlink(struct tcon_link *tlink) | |
| { | |
| if (!tlink || IS_ERR(tlink)) | |
| return; | |
| if (!atomic_dec_and_test(&tlink->tl_count) || | |
| test_bit(TCON_LINK_IN_TREE, &tlink->tl_flags)) { | |
| tlink->tl_time = jiffies; | |
| return; | |
| } | |
| if (!IS_ERR(tlink_tcon(tlink))) | |
| cifs_put_tcon(tlink_tcon(tlink), netfs_trace_tcon_ref_put_tlink); | |
| kfree(tlink); | |
| } | |
| static int | |
| compare_mount_options(struct super_block *sb, struct cifs_mnt_data *mnt_data) | |
| { | |
| struct cifs_sb_info *old = CIFS_SB(sb); | |
| struct cifs_sb_info *new = mnt_data->cifs_sb; | |
| unsigned int oldflags = cifs_sb_flags(old) & CIFS_MOUNT_MASK; | |
| unsigned int newflags = cifs_sb_flags(new) & CIFS_MOUNT_MASK; | |
| if ((sb->s_flags & CIFS_MS_MASK) != (mnt_data->flags & CIFS_MS_MASK)) | |
| return 0; | |
| if (old->mnt_cifs_serverino_autodisabled) | |
| newflags &= ~CIFS_MOUNT_SERVER_INUM; | |
| if (oldflags != newflags) | |
| return 0; | |
| /* | |
| * We want to share sb only if we don't specify an r/wsize or | |
| * specified r/wsize is greater than or equal to existing one. | |
| */ | |
| if (new->ctx->wsize && new->ctx->wsize < old->ctx->wsize) | |
| return 0; | |
| if (new->ctx->rsize && new->ctx->rsize < old->ctx->rsize) | |
| return 0; | |
| if (!uid_eq(old->ctx->linux_uid, new->ctx->linux_uid) || | |
| !gid_eq(old->ctx->linux_gid, new->ctx->linux_gid)) | |
| return 0; | |
| if (old->ctx->file_mode != new->ctx->file_mode || | |
| old->ctx->dir_mode != new->ctx->dir_mode) | |
| return 0; | |
| if (strcmp(old->local_nls->charset, new->local_nls->charset)) | |
| return 0; | |
| if (old->ctx->acregmax != new->ctx->acregmax) | |
| return 0; | |
| if (old->ctx->acdirmax != new->ctx->acdirmax) | |
| return 0; | |
| if (old->ctx->closetimeo != new->ctx->closetimeo) | |
| return 0; | |
| if (old->ctx->reparse_type != new->ctx->reparse_type) | |
| return 0; | |
| if (old->ctx->nonativesocket != new->ctx->nonativesocket) | |
| return 0; | |
| if (old->ctx->symlink_type != new->ctx->symlink_type) | |
| return 0; | |
| return 1; | |
| } | |
| static int match_prepath(struct super_block *sb, | |
| struct cifs_tcon *tcon, | |
| struct cifs_mnt_data *mnt_data) | |
| { | |
| struct smb3_fs_context *ctx = mnt_data->ctx; | |
| struct cifs_sb_info *old = CIFS_SB(sb); | |
| struct cifs_sb_info *new = mnt_data->cifs_sb; | |
| bool old_set = (cifs_sb_flags(old) & CIFS_MOUNT_USE_PREFIX_PATH) && | |
| old->prepath; | |
| bool new_set = (cifs_sb_flags(new) & CIFS_MOUNT_USE_PREFIX_PATH) && | |
| new->prepath; | |
| if (tcon->origin_fullpath && | |
| dfs_src_pathname_equal(tcon->origin_fullpath, ctx->source)) | |
| return 1; | |
| if (old_set && new_set && !strcmp(new->prepath, old->prepath)) | |
| return 1; | |
| else if (!old_set && !new_set) | |
| return 1; | |
| return 0; | |
| } | |
| int | |
| cifs_match_super(struct super_block *sb, struct fs_context *fc) | |
| { | |
| struct cifs_mnt_data *mnt_data = fc->sget_key; | |
| struct smb3_fs_context *ctx; | |
| struct cifs_sb_info *cifs_sb; | |
| struct TCP_Server_Info *tcp_srv; | |
| struct cifs_ses *ses; | |
| struct cifs_tcon *tcon; | |
| struct tcon_link *tlink; | |
| int rc = 0; | |
| spin_lock(&cifs_tcp_ses_lock); | |
| cifs_sb = CIFS_SB(sb); | |
| /* We do not want to use a superblock that has been shutdown */ | |
| if (cifs_forced_shutdown(cifs_sb)) { | |
| spin_unlock(&cifs_tcp_ses_lock); | |
| return 0; | |
| } | |
| tlink = cifs_get_tlink(cifs_sb_master_tlink(cifs_sb)); | |
| if (IS_ERR_OR_NULL(tlink)) { | |
| pr_warn_once("%s: skip super matching due to bad tlink(%p)\n", | |
| __func__, tlink); | |
| spin_unlock(&cifs_tcp_ses_lock); | |
| return 0; | |
| } | |
| tcon = tlink_tcon(tlink); | |
| ses = tcon->ses; | |
| tcp_srv = ses->server; | |
| ctx = mnt_data->ctx; | |
| spin_lock(&tcp_srv->srv_lock); | |
| spin_lock(&ses->ses_lock); | |
| spin_lock(&ses->chan_lock); | |
| spin_lock(&tcon->tc_lock); | |
| if (!match_server(tcp_srv, ctx, true) || | |
| !match_session(ses, ctx, true) || | |
| !match_tcon(tcon, ctx) || | |
| !match_prepath(sb, tcon, mnt_data)) { | |
| rc = 0; | |
| goto out; | |
| } | |
| rc = compare_mount_options(sb, mnt_data); | |
| out: | |
| spin_unlock(&tcon->tc_lock); | |
| spin_unlock(&ses->chan_lock); | |
| spin_unlock(&ses->ses_lock); | |
| spin_unlock(&tcp_srv->srv_lock); | |
| spin_unlock(&cifs_tcp_ses_lock); | |
| cifs_put_tlink(tlink); | |
| return rc; | |
| } | |
| #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) | |
| { | |
| struct sock *sk = sock->sk; | |
| BUG_ON(!sock_allow_reclassification(sk)); | |
| sock_lock_init_class_and_name(sk, "slock-AF_INET-CIFS", | |
| &cifs_slock_key[0], "sk_lock-AF_INET-CIFS", &cifs_key[0]); | |
| } | |
| static inline void | |
| cifs_reclassify_socket6(struct socket *sock) | |
| { | |
| struct sock *sk = sock->sk; | |
| BUG_ON(!sock_allow_reclassification(sk)); | |
| sock_lock_init_class_and_name(sk, "slock-AF_INET6-CIFS", | |
| &cifs_slock_key[1], "sk_lock-AF_INET6-CIFS", &cifs_key[1]); | |
| } | |
| #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) | |
| { | |
| unsigned int i, j; | |
| for (i = 0, j = 0; i < (length); i++) { | |
| /* mask a nibble at a time and encode */ | |
| target[j] = 'A' + (0x0F & (source[i] >> 4)); | |
| target[j+1] = 'A' + (0x0F & source[i]); | |
| j += 2; | |
| } | |
| } | |
| static int | |
| bind_socket(struct TCP_Server_Info *server) | |
| { | |
| int rc = 0; | |
| if (server->srcaddr.ss_family != AF_UNSPEC) { | |
| /* Bind to the specified local IP address */ | |
| struct socket *socket = server->ssocket; | |
| rc = kernel_bind(socket, | |
| (struct sockaddr_unsized *) &server->srcaddr, | |
| sizeof(server->srcaddr)); | |
| if (rc < 0) { | |
| struct sockaddr_in *saddr4; | |
| struct sockaddr_in6 *saddr6; | |
| saddr4 = (struct sockaddr_in *)&server->srcaddr; | |
| saddr6 = (struct sockaddr_in6 *)&server->srcaddr; | |
| if (saddr6->sin6_family == AF_INET6) | |
| cifs_server_dbg(VFS, "Failed to bind to: %pI6c, error: %d\n", | |
| &saddr6->sin6_addr, rc); | |
| else | |
| cifs_server_dbg(VFS, "Failed to bind to: %pI4, error: %d\n", | |
| &saddr4->sin_addr.s_addr, rc); | |
| } | |
| } | |
| return rc; | |
| } | |
| static int | |
| smb_recv_kvec(struct TCP_Server_Info *server, struct msghdr *msg, size_t *recv) | |
| { | |
| int rc = 0; | |
| int retries = 0; | |
| int msg_flags = server->noblocksnd ? MSG_DONTWAIT : 0; | |
| *recv = 0; | |
| while (msg_data_left(msg)) { | |
| rc = sock_recvmsg(server->ssocket, msg, msg_flags); | |
| if (rc == -EAGAIN) { | |
| retries++; | |
| if (retries >= 14 || | |
| (!server->noblocksnd && (retries > 2))) { | |
| cifs_server_dbg(VFS, "sends on sock %p stuck for 15 seconds\n", | |
| server->ssocket); | |
| return -EAGAIN; | |
| } | |
| msleep(1 << retries); | |
| continue; | |
| } | |
| if (rc < 0) | |
| return rc; | |
| if (rc == 0) { | |
| cifs_dbg(FYI, "Received no data (TCP RST)\n"); | |
| return -ECONNABORTED; | |
| } | |
| /* recv was at least partially successful */ | |
| *recv += rc; | |
| retries = 0; /* in case we get ENOSPC on the next send */ | |
| } | |
| return 0; | |
| } | |
| static int | |
| ip_rfc1001_connect(struct TCP_Server_Info *server) | |
| { | |
| int rc = 0; | |
| /* | |
| * some servers require RFC1001 sessinit before sending | |
| * negprot - BB check reconnection in case where second | |
| * sessinit is sent but no second negprot | |
| */ | |
| struct rfc1002_session_packet req = {}; | |
| struct rfc1002_session_packet resp = {}; | |
| struct msghdr msg = {}; | |
| struct kvec iov = {}; | |
| unsigned int len; | |
| size_t sent; | |
| size_t recv; | |
| req.trailer.session_req.called_len = sizeof(req.trailer.session_req.called_name); | |
| if (server->server_RFC1001_name[0] != 0) | |
| rfc1002mangle(req.trailer.session_req.called_name, | |
| server->server_RFC1001_name, | |
| RFC1001_NAME_LEN_WITH_NULL); | |
| else | |
| rfc1002mangle(req.trailer.session_req.called_name, | |
| DEFAULT_CIFS_CALLED_NAME, | |
| RFC1001_NAME_LEN_WITH_NULL); | |
| req.trailer.session_req.calling_len = sizeof(req.trailer.session_req.calling_name); | |
| /* calling name ends in null (byte 16) from old smb convention */ | |
| if (server->workstation_RFC1001_name[0] != 0) | |
| rfc1002mangle(req.trailer.session_req.calling_name, | |
| server->workstation_RFC1001_name, | |
| RFC1001_NAME_LEN_WITH_NULL); | |
| else | |
| rfc1002mangle(req.trailer.session_req.calling_name, | |
| "LINUX_CIFS_CLNT", | |
| RFC1001_NAME_LEN_WITH_NULL); | |
| /* | |
| * As per rfc1002, @len must be the number of bytes that follows the | |
| * length field of a rfc1002 session request payload. | |
| */ | |
| len = sizeof(req.trailer.session_req); | |
| req.type = RFC1002_SESSION_REQUEST; | |
| req.flags = 0; | |
| req.length = cpu_to_be16(len); | |
| len += offsetof(typeof(req), trailer.session_req); | |
| iov.iov_base = &req; | |
| iov.iov_len = len; | |
| iov_iter_kvec(&msg.msg_iter, ITER_SOURCE, &iov, 1, len); | |
| rc = smb_send_kvec(server, &msg, &sent); | |
| if (rc < 0 || len != sent) | |
| return (rc == -EINTR || rc == -EAGAIN) ? rc : -ECONNABORTED; | |
| /* | |
| * RFC1001 layer in at least one server requires very short break before | |
| * negprot presumably because not expecting negprot to follow so fast. | |
| * For example DOS SMB servers cannot process negprot if it was received | |
| * before the server sent response for SESSION_REQUEST packet. So, wait | |
| * for the response, read it and parse it as it can contain useful error | |
| * information (e.g. specified server name was incorrect). For example | |
| * even the latest Windows Server 2022 SMB1 server over port 139 send | |
| * error if its server name was in SESSION_REQUEST packet incorrect. | |
| * Nowadays usage of port 139 is not common, so waiting for reply here | |
| * does not slowing down mounting of common case (over port 445). | |
| */ | |
| len = offsetof(typeof(resp), trailer); | |
| iov.iov_base = &resp; | |
| iov.iov_len = len; | |
| iov_iter_kvec(&msg.msg_iter, ITER_DEST, &iov, 1, len); | |
| rc = smb_recv_kvec(server, &msg, &recv); | |
| if (rc < 0 || recv != len) | |
| return (rc == -EINTR || rc == -EAGAIN) ? rc : -ECONNABORTED; | |
| switch (resp.type) { | |
| case RFC1002_POSITIVE_SESSION_RESPONSE: | |
| if (be16_to_cpu(resp.length) != 0) { | |
| cifs_dbg(VFS, "RFC 1002 positive session response but with invalid non-zero length %u\n", | |
| be16_to_cpu(resp.length)); | |
| return smb_EIO(smb_eio_trace_rx_pos_sess_resp); | |
| } | |
| cifs_dbg(FYI, "RFC 1002 positive session response"); | |
| break; | |
| case RFC1002_NEGATIVE_SESSION_RESPONSE: | |
| /* Read RFC1002 response error code and convert it to errno in rc */ | |
| len = sizeof(resp.trailer.neg_ses_resp_error_code); | |
| iov.iov_base = &resp.trailer.neg_ses_resp_error_code; | |
| iov.iov_len = len; | |
| iov_iter_kvec(&msg.msg_iter, ITER_DEST, &iov, 1, len); | |
| if (be16_to_cpu(resp.length) == len && | |
| smb_recv_kvec(server, &msg, &recv) == 0 && | |
| recv == len) { | |
| cifs_dbg(VFS, "RFC 1002 negative session response with error 0x%x\n", | |
| resp.trailer.neg_ses_resp_error_code); | |
| switch (resp.trailer.neg_ses_resp_error_code) { | |
| case RFC1002_NOT_LISTENING_CALLED: | |
| /* server does not listen for specified server name */ | |
| fallthrough; | |
| case RFC1002_NOT_PRESENT: | |
| /* server name is incorrect */ | |
| rc = -ENOENT; | |
| cifs_dbg(VFS, "Server rejected NetBIOS servername %.15s\n", | |
| server->server_RFC1001_name[0] ? | |
| server->server_RFC1001_name : | |
| DEFAULT_CIFS_CALLED_NAME); | |
| cifs_dbg(VFS, "Specify correct NetBIOS servername in source path or with -o servern= option\n"); | |
| break; | |
| case RFC1002_NOT_LISTENING_CALLING: | |
| /* client name was not accepted by server */ | |
| rc = -EACCES; | |
| cifs_dbg(VFS, "Server rejected NetBIOS clientname %.15s\n", | |
| server->workstation_RFC1001_name[0] ? | |
| server->workstation_RFC1001_name : | |
| "LINUX_CIFS_CLNT"); | |
| cifs_dbg(VFS, "Specify correct NetBIOS clientname with -o netbiosname= option\n"); | |
| break; | |
| case RFC1002_INSUFFICIENT_RESOURCE: | |
| /* remote server resource error */ | |
| smb_EIO(smb_eio_trace_rx_insuff_res); | |
| rc = -EREMOTEIO; | |
| break; | |
| case RFC1002_UNSPECIFIED_ERROR: | |
| default: | |
| /* other/unknown error */ | |
| rc = smb_EIO(smb_eio_trace_rx_unspec_error); | |
| break; | |
| } | |
| } else { | |
| cifs_dbg(VFS, "RFC 1002 negative session response\n"); | |
| rc = smb_EIO(smb_eio_trace_rx_neg_sess_resp); | |
| } | |
| return rc; | |
| case RFC1002_RETARGET_SESSION_RESPONSE: | |
| cifs_dbg(VFS, "RFC 1002 retarget session response\n"); | |
| if (be16_to_cpu(resp.length) == sizeof(resp.trailer.retarget_resp)) { | |
| len = sizeof(resp.trailer.retarget_resp); | |
| iov.iov_base = &resp.trailer.retarget_resp; | |
| iov.iov_len = len; | |
| iov_iter_kvec(&msg.msg_iter, ITER_DEST, &iov, 1, len); | |
| if (smb_recv_kvec(server, &msg, &recv) == 0 && recv == len) { | |
| cifs_dbg(VFS, "Server wants to redirect connection\n"); | |
| cifs_dbg(VFS, "Remount with options -o ip=%pI4,port=%u\n", | |
| &resp.trailer.retarget_resp.retarget_ip_addr, | |
| be16_to_cpu(resp.trailer.retarget_resp.port)); | |
| } | |
| } | |
| cifs_dbg(VFS, "Closing connection\n"); | |
| /* FIXME: Should we automatically redirect to new retarget_resp server? */ | |
| return -EMULTIHOP; | |
| default: | |
| cifs_dbg(VFS, "RFC 1002 unknown response type 0x%x\n", resp.type); | |
| return smb_EIO1(smb_eio_trace_rx_unknown_resp, resp.type); | |
| } | |
| server->with_rfc1001 = true; | |
| return 0; | |
| } | |
| static int | |
| generic_ip_connect(struct TCP_Server_Info *server) | |
| { | |
| struct sockaddr *saddr; | |
| struct socket *socket; | |
| int slen, sfamily; | |
| __be16 sport; | |
| int rc = 0; | |
| saddr = (struct sockaddr *) &server->dstaddr; | |
| if (server->dstaddr.ss_family == AF_INET6) { | |
| struct sockaddr_in6 *ipv6 = (struct sockaddr_in6 *)&server->dstaddr; | |
| sport = ipv6->sin6_port; | |
| slen = sizeof(struct sockaddr_in6); | |
| sfamily = AF_INET6; | |
| cifs_dbg(FYI, "%s: connecting to [%pI6]:%d\n", __func__, &ipv6->sin6_addr, | |
| ntohs(sport)); | |
| } else { | |
| struct sockaddr_in *ipv4 = (struct sockaddr_in *)&server->dstaddr; | |
| sport = ipv4->sin_port; | |
| slen = sizeof(struct sockaddr_in); | |
| sfamily = AF_INET; | |
| cifs_dbg(FYI, "%s: connecting to %pI4:%d\n", __func__, &ipv4->sin_addr, | |
| ntohs(sport)); | |
| } | |
| if (server->ssocket) { | |
| socket = server->ssocket; | |
| } else { | |
| struct net *net = cifs_net_ns(server); | |
| struct sock *sk; | |
| rc = sock_create_kern(net, sfamily, SOCK_STREAM, | |
| IPPROTO_TCP, &server->ssocket); | |
| if (rc < 0) { | |
| cifs_server_dbg(VFS, "Error %d creating socket\n", rc); | |
| return rc; | |
| } | |
| sk = server->ssocket->sk; | |
| sk_net_refcnt_upgrade(sk); | |
| /* BB other socket options to set KEEPALIVE, NODELAY? */ | |
| cifs_dbg(FYI, "Socket created\n"); | |
| socket = server->ssocket; | |
| socket->sk->sk_allocation = GFP_NOFS; | |
| socket->sk->sk_use_task_frag = false; | |
| if (sfamily == AF_INET6) | |
| cifs_reclassify_socket6(socket); | |
| else | |
| cifs_reclassify_socket4(socket); | |
| } | |
| rc = bind_socket(server); | |
| if (rc < 0) | |
| return rc; | |
| /* | |
| * Eventually check for other socket options to change from | |
| * the default. sock_setsockopt not used because it expects | |
| * user space buffer | |
| */ | |
| socket->sk->sk_rcvtimeo = 7 * HZ; | |
| socket->sk->sk_sndtimeo = 5 * HZ; | |
| /* make the bufsizes depend on wsize/rsize and max requests */ | |
| if (server->noautotune) { | |
| if (socket->sk->sk_sndbuf < (200 * 1024)) | |
| socket->sk->sk_sndbuf = 200 * 1024; | |
| if (socket->sk->sk_rcvbuf < (140 * 1024)) | |
| socket->sk->sk_rcvbuf = 140 * 1024; | |
| } | |
| if (server->tcp_nodelay) | |
| tcp_sock_set_nodelay(socket->sk); | |
| cifs_dbg(FYI, "sndbuf %d rcvbuf %d rcvtimeo 0x%lx\n", | |
| socket->sk->sk_sndbuf, | |
| socket->sk->sk_rcvbuf, socket->sk->sk_rcvtimeo); | |
| rc = kernel_connect(socket, (struct sockaddr_unsized *)saddr, slen, | |
| server->noblockcnt ? O_NONBLOCK : 0); | |
| /* | |
| * When mounting SMB root file systems, we do not want to block in | |
| * connect. Otherwise bail out and then let cifs_reconnect() perform | |
| * reconnect failover - if possible. | |
| */ | |
| if (server->noblockcnt && rc == -EINPROGRESS) | |
| rc = 0; | |
| if (rc < 0) { | |
| cifs_dbg(FYI, "Error %d connecting to server\n", rc); | |
| trace_smb3_connect_err(server->hostname, server->conn_id, &server->dstaddr, rc); | |
| sock_release(socket); | |
| server->ssocket = NULL; | |
| return rc; | |
| } | |
| trace_smb3_connect_done(server->hostname, server->conn_id, &server->dstaddr); | |
| /* | |
| * Establish RFC1001 NetBIOS session when it was explicitly requested | |
| * by mount option -o nbsessinit, or when connecting to default RFC1001 | |
| * server port (139) and it was not explicitly disabled by mount option | |
| * -o nonbsessinit. | |
| */ | |
| if (server->with_rfc1001 || | |
| server->rfc1001_sessinit == 1 || | |
| (server->rfc1001_sessinit == -1 && sport == htons(RFC1001_PORT))) | |
| rc = ip_rfc1001_connect(server); | |
| return rc; | |
| } | |
| static int | |
| ip_connect(struct TCP_Server_Info *server) | |
| { | |
| __be16 *sport; | |
| struct sockaddr_in6 *addr6 = (struct sockaddr_in6 *)&server->dstaddr; | |
| struct sockaddr_in *addr = (struct sockaddr_in *)&server->dstaddr; | |
| if (server->dstaddr.ss_family == AF_INET6) | |
| sport = &addr6->sin6_port; | |
| else | |
| sport = &addr->sin_port; | |
| if (*sport == 0) { | |
| int rc; | |
| /* try with 445 port at first */ | |
| *sport = htons(CIFS_PORT); | |
| rc = generic_ip_connect(server); | |
| if (rc >= 0) | |
| return rc; | |
| /* if it failed, try with 139 port */ | |
| *sport = htons(RFC1001_PORT); | |
| } | |
| return generic_ip_connect(server); | |
| } | |
| int cifs_setup_cifs_sb(struct cifs_sb_info *cifs_sb) | |
| { | |
| struct smb3_fs_context *ctx = cifs_sb->ctx; | |
| unsigned int sbflags; | |
| int rc = 0; | |
| INIT_DELAYED_WORK(&cifs_sb->prune_tlinks, cifs_prune_tlinks); | |
| INIT_LIST_HEAD(&cifs_sb->tcon_sb_link); | |
| spin_lock_init(&cifs_sb->tlink_tree_lock); | |
| cifs_sb->tlink_tree = RB_ROOT; | |
| atomic_set(&cifs_sb->outstanding_rreq, 0); | |
| cifs_dbg(FYI, "file mode: %04ho dir mode: %04ho\n", | |
| ctx->file_mode, ctx->dir_mode); | |
| /* this is needed for ASCII cp to Unicode converts */ | |
| if (ctx->iocharset == NULL) { | |
| /* load_nls_default cannot return null */ | |
| cifs_sb->local_nls = load_nls_default(); | |
| } else { | |
| cifs_sb->local_nls = load_nls(ctx->iocharset); | |
| if (cifs_sb->local_nls == NULL) { | |
| cifs_dbg(VFS, "CIFS mount error: iocharset %s not found\n", | |
| ctx->iocharset); | |
| return -ELIBACC; | |
| } | |
| } | |
| ctx->local_nls = cifs_sb->local_nls; | |
| sbflags = smb3_update_mnt_flags(cifs_sb); | |
| if (ctx->direct_io) | |
| cifs_dbg(FYI, "mounting share using direct i/o\n"); | |
| if (ctx->cache_ro) { | |
| cifs_dbg(VFS, "mounting share with read only caching. Ensure that the share will not be modified while in use.\n"); | |
| sbflags |= CIFS_MOUNT_RO_CACHE; | |
| } else if (ctx->cache_rw) { | |
| cifs_dbg(VFS, "mounting share in single client RW caching mode. Ensure that no other systems will be accessing the share.\n"); | |
| sbflags |= CIFS_MOUNT_RO_CACHE | CIFS_MOUNT_RW_CACHE; | |
| } | |
| if ((ctx->cifs_acl) && (ctx->dynperm)) | |
| cifs_dbg(VFS, "mount option dynperm ignored if cifsacl mount option supported\n"); | |
| if (ctx->prepath) { | |
| cifs_sb->prepath = kstrdup(ctx->prepath, GFP_KERNEL); | |
| if (cifs_sb->prepath == NULL) | |
| rc = -ENOMEM; | |
| else | |
| sbflags |= CIFS_MOUNT_USE_PREFIX_PATH; | |
| } | |
| atomic_set(&cifs_sb->mnt_cifs_flags, sbflags); | |
| return rc; | |
| } | |
| /* Release all succeed connections */ | |
| void cifs_mount_put_conns(struct cifs_mount_ctx *mnt_ctx) | |
| { | |
| struct cifs_sb_info *cifs_sb = mnt_ctx->cifs_sb; | |
| int rc = 0; | |
| if (mnt_ctx->tcon) | |
| cifs_put_tcon(mnt_ctx->tcon, netfs_trace_tcon_ref_put_mnt_ctx); | |
| else if (mnt_ctx->ses) | |
| cifs_put_smb_ses(mnt_ctx->ses); | |
| else if (mnt_ctx->server) | |
| cifs_put_tcp_session(mnt_ctx->server, 0); | |
| mnt_ctx->ses = NULL; | |
| mnt_ctx->tcon = NULL; | |
| mnt_ctx->server = NULL; | |
| atomic_andnot(CIFS_MOUNT_POSIX_PATHS, &cifs_sb->mnt_cifs_flags); | |
| free_xid(mnt_ctx->xid); | |
| } | |
| int cifs_mount_get_session(struct cifs_mount_ctx *mnt_ctx) | |
| { | |
| struct TCP_Server_Info *server = NULL; | |
| struct smb3_fs_context *ctx; | |
| struct cifs_ses *ses = NULL; | |
| unsigned int xid; | |
| int rc = 0; | |
| xid = get_xid(); | |
| if (WARN_ON_ONCE(!mnt_ctx || !mnt_ctx->fs_ctx)) { | |
| rc = -EINVAL; | |
| goto out; | |
| } | |
| ctx = mnt_ctx->fs_ctx; | |
| /* get a reference to a tcp session */ | |
| server = cifs_get_tcp_session(ctx, NULL); | |
| if (IS_ERR(server)) { | |
| rc = PTR_ERR(server); | |
| server = NULL; | |
| goto out; | |
| } | |
| /* get a reference to a SMB session */ | |
| ses = cifs_get_smb_ses(server, ctx); | |
| if (IS_ERR(ses)) { | |
| rc = PTR_ERR(ses); | |
| ses = NULL; | |
| goto out; | |
| } | |
| if ((ctx->persistent == true) && (!(ses->server->capabilities & | |
| SMB2_GLOBAL_CAP_PERSISTENT_HANDLES))) { | |
| cifs_server_dbg(VFS, "persistent handles not supported by server\n"); | |
| rc = -EOPNOTSUPP; | |
| } | |
| out: | |
| mnt_ctx->xid = xid; | |
| mnt_ctx->server = server; | |
| mnt_ctx->ses = ses; | |
| mnt_ctx->tcon = NULL; | |
| return rc; | |
| } | |
| int cifs_mount_get_tcon(struct cifs_mount_ctx *mnt_ctx) | |
| { | |
| struct TCP_Server_Info *server; | |
| struct cifs_tcon *tcon = NULL; | |
| struct cifs_sb_info *cifs_sb; | |
| struct smb3_fs_context *ctx; | |
| unsigned int sbflags; | |
| int rc = 0; | |
| if (WARN_ON_ONCE(!mnt_ctx)) | |
| return -EINVAL; | |
| if (WARN_ON_ONCE(!mnt_ctx->server || !mnt_ctx->ses || | |
| !mnt_ctx->fs_ctx || !mnt_ctx->cifs_sb)) { | |
| mnt_ctx->tcon = NULL; | |
| return -EINVAL; | |
| } | |
| server = mnt_ctx->server; | |
| ctx = mnt_ctx->fs_ctx; | |
| cifs_sb = mnt_ctx->cifs_sb; | |
| /* search for existing tcon to this server share */ | |
| tcon = cifs_get_tcon(mnt_ctx->ses, ctx); | |
| if (IS_ERR(tcon)) { | |
| rc = PTR_ERR(tcon); | |
| tcon = NULL; | |
| goto out; | |
| } | |
| /* | |
| * if new SMB3.11 POSIX extensions are supported, do not change anything in the | |
| * path (i.e., do not remap / and \ and do not map any special characters) | |
| */ | |
| if (tcon->posix_extensions) { | |
| atomic_or(CIFS_MOUNT_POSIX_PATHS, &cifs_sb->mnt_cifs_flags); | |
| atomic_andnot(CIFS_MOUNT_MAP_SFM_CHR | | |
| CIFS_MOUNT_MAP_SPECIAL_CHR, | |
| &cifs_sb->mnt_cifs_flags); | |
| } | |
| #ifdef CONFIG_CIFS_ALLOW_INSECURE_LEGACY | |
| /* tell server which Unix caps we support */ | |
| if (cap_unix(tcon->ses)) { | |
| /* | |
| * reset of caps checks mount to see if unix extensions disabled | |
| * for just this mount. | |
| */ | |
| reset_cifs_unix_caps(mnt_ctx->xid, tcon, cifs_sb, ctx); | |
| spin_lock(&tcon->ses->server->srv_lock); | |
| if ((tcon->ses->server->tcpStatus == CifsNeedReconnect) && | |
| (le64_to_cpu(tcon->fsUnixInfo.Capability) & | |
| CIFS_UNIX_TRANSPORT_ENCRYPTION_MANDATORY_CAP)) { | |
| spin_unlock(&tcon->ses->server->srv_lock); | |
| rc = -EACCES; | |
| goto out; | |
| } | |
| spin_unlock(&tcon->ses->server->srv_lock); | |
| } else | |
| #endif /* CONFIG_CIFS_ALLOW_INSECURE_LEGACY */ | |
| tcon->unix_ext = 0; /* server does not support them */ | |
| sbflags = cifs_sb_flags(cifs_sb); | |
| /* do not care if a following call succeed - informational */ | |
| if (!tcon->pipe && server->ops->qfs_tcon) { | |
| server->ops->qfs_tcon(mnt_ctx->xid, tcon, cifs_sb); | |
| if (sbflags & CIFS_MOUNT_RO_CACHE) { | |
| if (tcon->fsDevInfo.DeviceCharacteristics & | |
| cpu_to_le32(FILE_READ_ONLY_DEVICE)) | |
| cifs_dbg(VFS, "mounted to read only share\n"); | |
| else if (!(sbflags & CIFS_MOUNT_RW_CACHE)) | |
| cifs_dbg(VFS, "read only mount of RW share\n"); | |
| /* no need to log a RW mount of a typical RW share */ | |
| } | |
| } | |
| cifs_negotiate_iosize(server, cifs_sb->ctx, tcon); | |
| /* | |
| * The cookie is initialized from volume info returned above. | |
| * Inside cifs_fscache_get_super_cookie it checks | |
| * that we do not get super cookie twice. | |
| */ | |
| if (sbflags & CIFS_MOUNT_FSCACHE) | |
| cifs_fscache_get_super_cookie(tcon); | |
| out: | |
| mnt_ctx->tcon = tcon; | |
| return rc; | |
| } | |
| static int mount_setup_tlink(struct cifs_sb_info *cifs_sb, struct cifs_ses *ses, | |
| struct cifs_tcon *tcon) | |
| { | |
| struct tcon_link *tlink; | |
| /* hang the tcon off of the superblock */ | |
| tlink = kzalloc_obj(*tlink); | |
| if (tlink == NULL) | |
| return -ENOMEM; | |
| tlink->tl_uid = ses->linux_uid; | |
| tlink->tl_tcon = tcon; | |
| tlink->tl_time = jiffies; | |
| set_bit(TCON_LINK_MASTER, &tlink->tl_flags); | |
| set_bit(TCON_LINK_IN_TREE, &tlink->tl_flags); | |
| cifs_sb->master_tlink = tlink; | |
| spin_lock(&cifs_sb->tlink_tree_lock); | |
| tlink_rb_insert(&cifs_sb->tlink_tree, tlink); | |
| spin_unlock(&cifs_sb->tlink_tree_lock); | |
| spin_lock(&tcon->sb_list_lock); | |
| list_add(&cifs_sb->tcon_sb_link, &tcon->cifs_sb_list); | |
| spin_unlock(&tcon->sb_list_lock); | |
| queue_delayed_work(cifsiod_wq, &cifs_sb->prune_tlinks, | |
| TLINK_IDLE_EXPIRE); | |
| return 0; | |
| } | |
| 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) | |
| { | |
| int rc; | |
| char *s; | |
| char sep, tmp; | |
| int skip = added_treename ? 1 : 0; | |
| sep = CIFS_DIR_SEP(cifs_sb); | |
| s = full_path; | |
| rc = server->ops->is_path_accessible(xid, tcon, cifs_sb, ""); | |
| while (rc == 0) { | |
| /* skip separators */ | |
| while (*s == sep) | |
| s++; | |
| if (!*s) | |
| break; | |
| /* next separator */ | |
| while (*s && *s != sep) | |
| s++; | |
| /* | |
| * if the treename is added, we then have to skip the first | |
| * part within the separators | |
| */ | |
| if (skip) { | |
| skip = 0; | |
| continue; | |
| } | |
| /* | |
| * temporarily null-terminate the path at the end of | |
| * the current component | |
| */ | |
| tmp = *s; | |
| *s = 0; | |
| rc = server->ops->is_path_accessible(xid, tcon, cifs_sb, | |
| full_path); | |
| *s = tmp; | |
| } | |
| return rc; | |
| } | |
| /* | |
| * 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) | |
| { | |
| int rc; | |
| struct cifs_sb_info *cifs_sb = mnt_ctx->cifs_sb; | |
| struct TCP_Server_Info *server = mnt_ctx->server; | |
| unsigned int xid = mnt_ctx->xid; | |
| struct cifs_tcon *tcon = mnt_ctx->tcon; | |
| struct smb3_fs_context *ctx = mnt_ctx->fs_ctx; | |
| char *full_path; | |
| if (!server->ops->is_path_accessible) | |
| return -EOPNOTSUPP; | |
| /* | |
| * cifs_build_path_to_root works only when we have a valid tcon | |
| */ | |
| full_path = cifs_build_path_to_root(ctx, cifs_sb, tcon, | |
| tcon->Flags & SMB_SHARE_IS_IN_DFS); | |
| if (full_path == NULL) | |
| return -ENOMEM; | |
| cifs_dbg(FYI, "%s: full_path: %s\n", __func__, full_path); | |
| rc = server->ops->is_path_accessible(xid, tcon, cifs_sb, | |
| full_path); | |
| if (rc != 0 && rc != -EREMOTE) | |
| goto out; | |
| if (rc != -EREMOTE) { | |
| rc = cifs_are_all_path_components_accessible(server, xid, tcon, | |
| cifs_sb, full_path, tcon->Flags & SMB_SHARE_IS_IN_DFS); | |
| if (rc != 0) { | |
| cifs_server_dbg(VFS, "cannot query dirs between root and final path, enabling CIFS_MOUNT_USE_PREFIX_PATH\n"); | |
| atomic_or(CIFS_MOUNT_USE_PREFIX_PATH, | |
| &cifs_sb->mnt_cifs_flags); | |
| rc = 0; | |
| } | |
| } | |
| out: | |
| kfree(full_path); | |
| return rc; | |
| } | |
| static struct mchan_mount * | |
| mchan_mount_alloc(struct cifs_ses *ses) | |
| { | |
| struct mchan_mount *mchan_mount; | |
| mchan_mount = kzalloc_obj(*mchan_mount); | |
| if (!mchan_mount) | |
| return ERR_PTR(-ENOMEM); | |
| INIT_WORK(&mchan_mount->work, mchan_mount_work_fn); | |
| spin_lock(&cifs_tcp_ses_lock); | |
| cifs_smb_ses_inc_refcount(ses); | |
| spin_unlock(&cifs_tcp_ses_lock); | |
| mchan_mount->ses = ses; | |
| return mchan_mount; | |
| } | |
| static void | |
| mchan_mount_free(struct mchan_mount *mchan_mount) | |
| { | |
| cifs_put_smb_ses(mchan_mount->ses); | |
| kfree(mchan_mount); | |
| } | |
| static void | |
| mchan_mount_work_fn(struct work_struct *work) | |
| { | |
| struct mchan_mount *mchan_mount = container_of(work, struct mchan_mount, work); | |
| smb3_update_ses_channels(mchan_mount->ses, | |
| mchan_mount->ses->server, | |
| false /* from_reconnect */, | |
| false /* disable_mchan */); | |
| mchan_mount_free(mchan_mount); | |
| } | |
| #ifdef CONFIG_CIFS_DFS_UPCALL | |
| int cifs_mount(struct cifs_sb_info *cifs_sb, struct smb3_fs_context *ctx) | |
| { | |
| struct cifs_mount_ctx mnt_ctx = { .cifs_sb = cifs_sb, .fs_ctx = ctx, }; | |
| struct mchan_mount *mchan_mount = NULL; | |
| int rc; | |
| rc = dfs_mount_share(&mnt_ctx); | |
| if (rc) | |
| goto error; | |
| if (ctx->multichannel) { | |
| mchan_mount = mchan_mount_alloc(mnt_ctx.ses); | |
| if (IS_ERR(mchan_mount)) { | |
| rc = PTR_ERR(mchan_mount); | |
| goto error; | |
| } | |
| } | |
| if (!ctx->dfs_conn) | |
| goto out; | |
| /* | |
| * After reconnecting to a different server, unique ids won't match anymore, so we disable | |
| * serverino. This prevents dentry revalidation to think the dentry are stale (ESTALE). | |
| */ | |
| cifs_autodisable_serverino(cifs_sb, "DFS failover may potentially connect to a different server, inode numbers won't match anymore", 0); | |
| /* | |
| * Force the use of prefix path to support failover on DFS paths that resolve to targets | |
| * that have different prefix paths. | |
| */ | |
| atomic_or(CIFS_MOUNT_USE_PREFIX_PATH, &cifs_sb->mnt_cifs_flags); | |
| kfree(cifs_sb->prepath); | |
| cifs_sb->prepath = ctx->prepath; | |
| ctx->prepath = NULL; | |
| out: | |
| rc = mount_setup_tlink(cifs_sb, mnt_ctx.ses, mnt_ctx.tcon); | |
| if (rc) | |
| goto error; | |
| if (ctx->multichannel) | |
| queue_work(cifsiod_wq, &mchan_mount->work); | |
| free_xid(mnt_ctx.xid); | |
| return rc; | |
| error: | |
| if (ctx->multichannel && !IS_ERR_OR_NULL(mchan_mount)) | |
| mchan_mount_free(mchan_mount); | |
| cifs_mount_put_conns(&mnt_ctx); | |
| return rc; | |
| } | |
| #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, }; | |
| struct mchan_mount *mchan_mount = NULL; | |
| 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; | |
| if (ctx->multichannel) { | |
| mchan_mount = mchan_mount_alloc(mnt_ctx.ses); | |
| if (IS_ERR(mchan_mount)) { | |
| rc = PTR_ERR(mchan_mount); | |
| goto error; | |
| } | |
| } | |
| rc = mount_setup_tlink(cifs_sb, mnt_ctx.ses, mnt_ctx.tcon); | |
| if (rc) | |
| goto error; | |
| if (ctx->multichannel) | |
| queue_work(cifsiod_wq, &mchan_mount->work); | |
| free_xid(mnt_ctx.xid); | |
| return rc; | |
| error: | |
| if (ctx->multichannel && !IS_ERR_OR_NULL(mchan_mount)) | |
| mchan_mount_free(mchan_mount); | |
| cifs_mount_put_conns(&mnt_ctx); | |
| return rc; | |
| } | |
| #endif | |
| static void delayed_free(struct rcu_head *p) | |
| { | |
| struct cifs_sb_info *cifs_sb = container_of(p, struct cifs_sb_info, rcu); | |
| unload_nls(cifs_sb->local_nls); | |
| smb3_cleanup_fs_context(cifs_sb->ctx); | |
| kfree(cifs_sb); | |
| } | |
| void | |
| cifs_umount(struct cifs_sb_info *cifs_sb) | |
| { | |
| struct rb_root *root = &cifs_sb->tlink_tree; | |
| struct rb_node *node; | |
| struct tcon_link *tlink; | |
| struct cifs_tcon *tcon = NULL; | |
| cancel_delayed_work_sync(&cifs_sb->prune_tlinks); | |
| if (cifs_sb->master_tlink) { | |
| tcon = cifs_sb->master_tlink->tl_tcon; | |
| if (tcon) { | |
| spin_lock(&tcon->sb_list_lock); | |
| list_del_init(&cifs_sb->tcon_sb_link); | |
| spin_unlock(&tcon->sb_list_lock); | |
| } | |
| } | |
| spin_lock(&cifs_sb->tlink_tree_lock); | |
| while ((node = rb_first(root))) { | |
| tlink = rb_entry(node, struct tcon_link, tl_rbnode); | |
| cifs_get_tlink(tlink); | |
| clear_bit(TCON_LINK_IN_TREE, &tlink->tl_flags); | |
| rb_erase(node, root); | |
| spin_unlock(&cifs_sb->tlink_tree_lock); | |
| cifs_put_tlink(tlink); | |
| spin_lock(&cifs_sb->tlink_tree_lock); | |
| } | |
| spin_unlock(&cifs_sb->tlink_tree_lock); | |
| kfree(cifs_sb->prepath); | |
| call_rcu(&cifs_sb->rcu, delayed_free); | |
| } | |
| int | |
| cifs_negotiate_protocol(const unsigned int xid, struct cifs_ses *ses, | |
| struct TCP_Server_Info *server) | |
| { | |
| bool in_retry = false; | |
| int rc = 0; | |
| if (!server->ops->need_neg || !server->ops->negotiate) | |
| return -ENOSYS; | |
| retry: | |
| /* only send once per connect */ | |
| spin_lock(&server->srv_lock); | |
| if (server->tcpStatus != CifsGood && | |
| server->tcpStatus != CifsNew && | |
| server->tcpStatus != CifsNeedNegotiate) { | |
| spin_unlock(&server->srv_lock); | |
| return -EHOSTDOWN; | |
| } | |
| if (!server->ops->need_neg(server) && | |
| server->tcpStatus == CifsGood) { | |
| spin_unlock(&server->srv_lock); | |
| return 0; | |
| } | |
| server->tcpStatus = CifsInNegotiate; | |
| server->neg_start = jiffies; | |
| spin_unlock(&server->srv_lock); | |
| rc = server->ops->negotiate(xid, ses, server); | |
| if (rc == -EAGAIN) { | |
| /* Allow one retry attempt */ | |
| if (!in_retry) { | |
| in_retry = true; | |
| goto retry; | |
| } | |
| rc = -EHOSTDOWN; | |
| } | |
| if (rc == 0) { | |
| spin_lock(&server->srv_lock); | |
| if (server->tcpStatus == CifsInNegotiate) | |
| server->tcpStatus = CifsGood; | |
| else | |
| rc = -EHOSTDOWN; | |
| spin_unlock(&server->srv_lock); | |
| } else { | |
| spin_lock(&server->srv_lock); | |
| if (server->tcpStatus == CifsInNegotiate) | |
| server->tcpStatus = CifsNeedNegotiate; | |
| spin_unlock(&server->srv_lock); | |
| } | |
| return rc; | |
| } | |
| int | |
| cifs_setup_session(const unsigned int xid, struct cifs_ses *ses, | |
| struct TCP_Server_Info *server, | |
| struct nls_table *nls_info) | |
| { | |
| int rc = 0; | |
| struct TCP_Server_Info *pserver = SERVER_IS_CHAN(server) ? server->primary_server : server; | |
| struct sockaddr_in6 *addr6 = (struct sockaddr_in6 *)&pserver->dstaddr; | |
| struct sockaddr_in *addr = (struct sockaddr_in *)&pserver->dstaddr; | |
| bool is_binding = false; | |
| bool new_ses; | |
| spin_lock(&ses->ses_lock); | |
| new_ses = ses->ses_status == SES_NEW; | |
| cifs_dbg(FYI, "%s: channel connect bitmap: 0x%lx\n", | |
| __func__, ses->chans_need_reconnect); | |
| if (ses->ses_status != SES_GOOD && | |
| ses->ses_status != SES_NEW && | |
| ses->ses_status != SES_NEED_RECON) { | |
| spin_unlock(&ses->ses_lock); | |
| return -EHOSTDOWN; | |
| } | |
| /* only send once per connect */ | |
| spin_lock(&ses->chan_lock); | |
| if (CIFS_ALL_CHANS_GOOD(ses)) { | |
| if (ses->ses_status == SES_NEED_RECON) | |
| ses->ses_status = SES_GOOD; | |
| spin_unlock(&ses->chan_lock); | |
| spin_unlock(&ses->ses_lock); | |
| return 0; | |
| } | |
| cifs_chan_set_in_reconnect(ses, server); | |
| is_binding = !CIFS_ALL_CHANS_NEED_RECONNECT(ses); | |
| spin_unlock(&ses->chan_lock); | |
| if (!is_binding) { | |
| ses->ses_status = SES_IN_SETUP; | |
| /* force iface_list refresh */ | |
| spin_lock(&ses->iface_lock); | |
| ses->iface_last_update = 0; | |
| spin_unlock(&ses->iface_lock); | |
| } | |
| spin_unlock(&ses->ses_lock); | |
| /* update ses ip_addr only for primary chan */ | |
| if (server == pserver) { | |
| if (server->dstaddr.ss_family == AF_INET6) | |
| scnprintf(ses->ip_addr, sizeof(ses->ip_addr), "%pI6", &addr6->sin6_addr); | |
| else | |
| scnprintf(ses->ip_addr, sizeof(ses->ip_addr), "%pI4", &addr->sin_addr); | |
| } | |
| if (!is_binding) { | |
| ses->capabilities = server->capabilities; | |
| if (!linuxExtEnabled) | |
| ses->capabilities &= (~server->vals->cap_unix); | |
| /* | |
| * Check if the server supports specified encoding mode. | |
| * Zero value in vals->cap_unicode indidcates that chosen | |
| * protocol dialect does not support non-UNICODE mode. | |
| */ | |
| if (ses->unicode == 1 && server->vals->cap_unicode != 0 && | |
| !(server->capabilities & server->vals->cap_unicode)) { | |
| cifs_dbg(VFS, "Server does not support mounting in UNICODE mode\n"); | |
| rc = -EOPNOTSUPP; | |
| } else if (ses->unicode == 0 && server->vals->cap_unicode == 0) { | |
| cifs_dbg(VFS, "Server does not support mounting in non-UNICODE mode\n"); | |
| rc = -EOPNOTSUPP; | |
| } else if (ses->unicode == 0) { | |
| /* | |
| * When UNICODE mode was explicitly disabled then | |
| * do not announce client UNICODE capability. | |
| */ | |
| ses->capabilities &= (~server->vals->cap_unicode); | |
| } | |
| if (ses->auth_key.response) { | |
| cifs_dbg(FYI, "Free previous auth_key.response = %p\n", | |
| ses->auth_key.response); | |
| kfree_sensitive(ses->auth_key.response); | |
| ses->auth_key.response = NULL; | |
| ses->auth_key.len = 0; | |
| } | |
| } | |
| cifs_dbg(FYI, "Security Mode: 0x%x Capabilities: 0x%x TimeAdjust: %d\n", | |
| server->sec_mode, server->capabilities, server->timeAdj); | |
| if (!rc) { | |
| if (server->ops->sess_setup) | |
| rc = server->ops->sess_setup(xid, ses, server, nls_info); | |
| else | |
| rc = -ENOSYS; | |
| } | |
| if (rc) { | |
| if (new_ses) { | |
| cifs_server_dbg(VFS, "failed to create a new SMB session with %s: %d\n", | |
| get_security_type_str(ses->sectype), rc); | |
| } | |
| spin_lock(&ses->ses_lock); | |
| if (ses->ses_status == SES_IN_SETUP) | |
| ses->ses_status = SES_NEED_RECON; | |
| spin_lock(&ses->chan_lock); | |
| cifs_chan_clear_in_reconnect(ses, server); | |
| spin_unlock(&ses->chan_lock); | |
| spin_unlock(&ses->ses_lock); | |
| } else { | |
| spin_lock(&ses->ses_lock); | |
| if (ses->ses_status == SES_IN_SETUP) | |
| ses->ses_status = SES_GOOD; | |
| spin_lock(&ses->chan_lock); | |
| cifs_chan_clear_in_reconnect(ses, server); | |
| cifs_chan_clear_need_reconnect(ses, server); | |
| spin_unlock(&ses->chan_lock); | |
| spin_unlock(&ses->ses_lock); | |
| } | |
| return rc; | |
| } | |
| static int set_fs_context_auth(struct smb3_fs_context *ctx, | |
| struct cifs_ses *ses) | |
| { | |
| ctx->sectype = ses->sectype; | |
| /* | |
| * krb5 is special as we might need to pass username (passwordless) down | |
| * to cifs.upcall(8) for keytab. | |
| */ | |
| if (ctx->sectype == Kerberos) { | |
| if (ses->user_name && ses->user_name[0]) { | |
| ctx->username = kstrndup(ses->user_name, | |
| CIFS_MAX_USERNAME_LEN, | |
| GFP_KERNEL); | |
| if (!ctx->username) | |
| return -ENOMEM; | |
| } | |
| return 0; | |
| } | |
| return cifs_set_cifscreds(ctx, ses); | |
| } | |
| static struct cifs_tcon * | |
| cifs_construct_tcon(struct cifs_sb_info *cifs_sb, kuid_t fsuid) | |
| { | |
| int rc; | |
| struct cifs_tcon *master_tcon = cifs_sb_master_tcon(cifs_sb); | |
| struct cifs_ses *ses; | |
| struct cifs_tcon *tcon = NULL; | |
| struct smb3_fs_context *ctx; | |
| char *origin_fullpath = NULL; | |
| ctx = kzalloc_obj(*ctx); | |
| if (ctx == NULL) | |
| return ERR_PTR(-ENOMEM); | |
| ctx->local_nls = cifs_sb->local_nls; | |
| ctx->linux_uid = fsuid; | |
| ctx->cred_uid = fsuid; | |
| ctx->UNC = master_tcon->tree_name; | |
| ctx->retry = master_tcon->retry; | |
| ctx->nocase = master_tcon->nocase; | |
| ctx->nohandlecache = master_tcon->nohandlecache; | |
| ctx->local_lease = master_tcon->local_lease; | |
| ctx->no_lease = master_tcon->no_lease; | |
| ctx->resilient = master_tcon->use_resilient; | |
| ctx->persistent = master_tcon->use_persistent; | |
| ctx->handle_timeout = master_tcon->handle_timeout; | |
| ctx->no_linux_ext = !master_tcon->unix_ext; | |
| ctx->linux_ext = master_tcon->posix_extensions; | |
| ctx->sectype = master_tcon->ses->sectype; | |
| ctx->sign = master_tcon->ses->sign; | |
| ctx->seal = master_tcon->seal; | |
| ctx->witness = master_tcon->use_witness; | |
| ctx->dfs_root_ses = master_tcon->ses->dfs_root_ses; | |
| ctx->unicode = master_tcon->ses->unicode; | |
| rc = set_fs_context_auth(ctx, master_tcon->ses); | |
| if (rc) { | |
| tcon = ERR_PTR(rc); | |
| goto out; | |
| } | |
| /* get a reference for the same TCP session */ | |
| spin_lock(&cifs_tcp_ses_lock); | |
| ++master_tcon->ses->server->srv_count; | |
| spin_unlock(&cifs_tcp_ses_lock); | |
| ses = cifs_get_smb_ses(master_tcon->ses->server, ctx); | |
| if (IS_ERR(ses)) { | |
| tcon = ERR_CAST(ses); | |
| cifs_put_tcp_session(master_tcon->ses->server, 0); | |
| goto out; | |
| } | |
| #ifdef CONFIG_CIFS_DFS_UPCALL | |
| spin_lock(&master_tcon->tc_lock); | |
| if (master_tcon->origin_fullpath) { | |
| spin_unlock(&master_tcon->tc_lock); | |
| origin_fullpath = dfs_get_path(cifs_sb, cifs_sb->ctx->source); | |
| if (IS_ERR(origin_fullpath)) { | |
| tcon = ERR_CAST(origin_fullpath); | |
| origin_fullpath = NULL; | |
| cifs_put_smb_ses(ses); | |
| goto out; | |
| } | |
| } else { | |
| spin_unlock(&master_tcon->tc_lock); | |
| } | |
| #endif | |
| tcon = cifs_get_tcon(ses, ctx); | |
| if (IS_ERR(tcon)) { | |
| cifs_put_smb_ses(ses); | |
| goto out; | |
| } | |
| #ifdef CONFIG_CIFS_DFS_UPCALL | |
| if (origin_fullpath) { | |
| spin_lock(&tcon->tc_lock); | |
| tcon->origin_fullpath = origin_fullpath; | |
| spin_unlock(&tcon->tc_lock); | |
| origin_fullpath = NULL; | |
| queue_delayed_work(dfscache_wq, &tcon->dfs_cache_work, | |
| dfs_cache_get_ttl() * HZ); | |
| } | |
| #endif | |
| #ifdef CONFIG_CIFS_ALLOW_INSECURE_LEGACY | |
| if (cap_unix(ses)) | |
| reset_cifs_unix_caps(0, tcon, NULL, ctx); | |
| #endif /* CONFIG_CIFS_ALLOW_INSECURE_LEGACY */ | |
| out: | |
| kfree(ctx->username); | |
| kfree(ctx->domainname); | |
| kfree_sensitive(ctx->password); | |
| kfree(origin_fullpath); | |
| kfree(ctx); | |
| return tcon; | |
| } | |
| struct cifs_tcon * | |
| cifs_sb_master_tcon(struct cifs_sb_info *cifs_sb) | |
| { | |
| return tlink_tcon(cifs_sb_master_tlink(cifs_sb)); | |
| } | |
| /* find and return a tlink with given uid */ | |
| static struct tcon_link * | |
| tlink_rb_search(struct rb_root *root, kuid_t uid) | |
| { | |
| struct rb_node *node = root->rb_node; | |
| struct tcon_link *tlink; | |
| while (node) { | |
| tlink = rb_entry(node, struct tcon_link, tl_rbnode); | |
| if (uid_gt(tlink->tl_uid, uid)) | |
| node = node->rb_left; | |
| else if (uid_lt(tlink->tl_uid, uid)) | |
| node = node->rb_right; | |
| else | |
| return tlink; | |
| } | |
| return NULL; | |
| } | |
| /* insert a tcon_link into the tree */ | |
| static void | |
| tlink_rb_insert(struct rb_root *root, struct tcon_link *new_tlink) | |
| { | |
| struct rb_node **new = &(root->rb_node), *parent = NULL; | |
| struct tcon_link *tlink; | |
| while (*new) { | |
| tlink = rb_entry(*new, struct tcon_link, tl_rbnode); | |
| parent = *new; | |
| if (uid_gt(tlink->tl_uid, new_tlink->tl_uid)) | |
| new = &((*new)->rb_left); | |
| else | |
| new = &((*new)->rb_right); | |
| } | |
| rb_link_node(&new_tlink->tl_rbnode, parent, new); | |
| rb_insert_color(&new_tlink->tl_rbnode, root); | |
| } | |
| /* | |
| * 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. | |
| * | |
| * REMEMBER to call cifs_put_tlink() after successful calls to cifs_sb_tlink, | |
| * to avoid refcount issues | |
| */ | |
| struct tcon_link * | |
| cifs_sb_tlink(struct cifs_sb_info *cifs_sb) | |
| { | |
| struct tcon_link *tlink, *newtlink; | |
| kuid_t fsuid = current_fsuid(); | |
| int err; | |
| if (!(cifs_sb_flags(cifs_sb) & CIFS_MOUNT_MULTIUSER)) | |
| return cifs_get_tlink(cifs_sb_master_tlink(cifs_sb)); | |
| spin_lock(&cifs_sb->tlink_tree_lock); | |
| tlink = tlink_rb_search(&cifs_sb->tlink_tree, fsuid); | |
| if (tlink) | |
| cifs_get_tlink(tlink); | |
| spin_unlock(&cifs_sb->tlink_tree_lock); | |
| if (tlink == NULL) { | |
| newtlink = kzalloc_obj(*tlink); | |
| if (newtlink == NULL) | |
| return ERR_PTR(-ENOMEM); | |
| newtlink->tl_uid = fsuid; | |
| newtlink->tl_tcon = ERR_PTR(-EACCES); | |
| set_bit(TCON_LINK_PENDING, &newtlink->tl_flags); | |
| set_bit(TCON_LINK_IN_TREE, &newtlink->tl_flags); | |
| cifs_get_tlink(newtlink); | |
| spin_lock(&cifs_sb->tlink_tree_lock); | |
| /* was one inserted after previous search? */ | |
| tlink = tlink_rb_search(&cifs_sb->tlink_tree, fsuid); | |
| if (tlink) { | |
| cifs_get_tlink(tlink); | |
| spin_unlock(&cifs_sb->tlink_tree_lock); | |
| kfree(newtlink); | |
| goto wait_for_construction; | |
| } | |
| tlink = newtlink; | |
| tlink_rb_insert(&cifs_sb->tlink_tree, tlink); | |
| spin_unlock(&cifs_sb->tlink_tree_lock); | |
| } else { | |
| wait_for_construction: | |
| err = wait_on_bit(&tlink->tl_flags, TCON_LINK_PENDING, | |
| TASK_INTERRUPTIBLE); | |
| if (err) { | |
| cifs_put_tlink(tlink); | |
| return ERR_PTR(-ERESTARTSYS); | |
| } | |
| /* if it's good, return it */ | |
| if (!IS_ERR(tlink->tl_tcon)) | |
| return tlink; | |
| /* return error if we tried this already recently */ | |
| if (time_before(jiffies, tlink->tl_time + TLINK_ERROR_EXPIRE)) { | |
| err = PTR_ERR(tlink->tl_tcon); | |
| cifs_put_tlink(tlink); | |
| return ERR_PTR(err); | |
| } | |
| if (test_and_set_bit(TCON_LINK_PENDING, &tlink->tl_flags)) | |
| goto wait_for_construction; | |
| } | |
| tlink->tl_tcon = cifs_construct_tcon(cifs_sb, fsuid); | |
| clear_bit(TCON_LINK_PENDING, &tlink->tl_flags); | |
| wake_up_bit(&tlink->tl_flags, TCON_LINK_PENDING); | |
| if (IS_ERR(tlink->tl_tcon)) { | |
| err = PTR_ERR(tlink->tl_tcon); | |
| if (err == -ENOKEY) | |
| err = -EACCES; | |
| cifs_put_tlink(tlink); | |
| return ERR_PTR(err); | |
| } | |
| return tlink; | |
| } | |
| /* | |
| * periodic workqueue job that scans tcon_tree for a superblock and closes | |
| * out tcons. | |
| */ | |
| static void | |
| cifs_prune_tlinks(struct work_struct *work) | |
| { | |
| struct cifs_sb_info *cifs_sb = container_of(work, struct cifs_sb_info, | |
| prune_tlinks.work); | |
| struct rb_root *root = &cifs_sb->tlink_tree; | |
| struct rb_node *node; | |
| struct rb_node *tmp; | |
| struct tcon_link *tlink; | |
| /* | |
| * Because we drop the spinlock in the loop in order to put the tlink | |
| * it's not guarded against removal of links from the tree. The only | |
| * places that remove entries from the tree are this function and | |
| * umounts. Because this function is non-reentrant and is canceled | |
| * before umount can proceed, this is safe. | |
| */ | |
| spin_lock(&cifs_sb->tlink_tree_lock); | |
| node = rb_first(root); | |
| while (node != NULL) { | |
| tmp = node; | |
| node = rb_next(tmp); | |
| tlink = rb_entry(tmp, struct tcon_link, tl_rbnode); | |
| if (test_bit(TCON_LINK_MASTER, &tlink->tl_flags) || | |
| atomic_read(&tlink->tl_count) != 0 || | |
| time_after(tlink->tl_time + TLINK_IDLE_EXPIRE, jiffies)) | |
| continue; | |
| cifs_get_tlink(tlink); | |
| clear_bit(TCON_LINK_IN_TREE, &tlink->tl_flags); | |
| rb_erase(tmp, root); | |
| spin_unlock(&cifs_sb->tlink_tree_lock); | |
| cifs_put_tlink(tlink); | |
| spin_lock(&cifs_sb->tlink_tree_lock); | |
| } | |
| spin_unlock(&cifs_sb->tlink_tree_lock); | |
| queue_delayed_work(cifsiod_wq, &cifs_sb->prune_tlinks, | |
| TLINK_IDLE_EXPIRE); | |
| } | |
| #ifndef CONFIG_CIFS_DFS_UPCALL | |
| int cifs_tree_connect(const unsigned int xid, struct cifs_tcon *tcon) | |
| { | |
| const struct smb_version_operations *ops = tcon->ses->server->ops; | |
| int rc; | |
| /* 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, tcon->ses->local_nls); | |
| 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 |