diff options
Diffstat (limited to 'fs/smb/client/connect.c')
-rw-r--r-- | fs/smb/client/connect.c | 206 |
1 files changed, 187 insertions, 19 deletions
diff --git a/fs/smb/client/connect.c b/fs/smb/client/connect.c index 73f93a35eedd..d7bad2c3af37 100644 --- a/fs/smb/client/connect.c +++ b/fs/smb/client/connect.c @@ -300,6 +300,7 @@ cifs_abort_connection(struct TCP_Server_Info *server) server->ssocket->flags); sock_release(server->ssocket); server->ssocket = NULL; + put_net(cifs_net_ns(server)); } server->sequence_number = 0; server->session_estab = false; @@ -1676,6 +1677,7 @@ cifs_get_tcp_session(struct smb3_fs_context *ctx, /* Grab netns reference for this server. */ 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; @@ -2349,6 +2351,7 @@ retry_old_session: ses->cred_uid = ctx->cred_uid; ses->linux_uid = ctx->linux_uid; + ses->unicode = ctx->unicode; ses->sectype = ctx->sectype; ses->sign = ctx->sign; @@ -3026,6 +3029,44 @@ bind_socket(struct TCP_Server_Info *server) } 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; @@ -3035,8 +3076,12 @@ ip_rfc1001_connect(struct TCP_Server_Info *server) * sessinit is sent but no second negprot */ struct rfc1002_session_packet req = {}; - struct smb_hdr *smb_buf = (struct smb_hdr *)&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); @@ -3065,19 +3110,118 @@ ip_rfc1001_connect(struct TCP_Server_Info *server) * As per rfc1002, @len must be the number of bytes that follows the * length field of a rfc1002 session request payload. */ - len = sizeof(req) - offsetof(struct rfc1002_session_packet, trailer.session_req); + 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; - smb_buf->smb_buf_length = cpu_to_be32((RFC1002_SESSION_REQUEST << 24) | len); - rc = smb_send(server, smb_buf, len); /* * RFC1001 layer in at least one server requires very short break before * negprot presumably because not expecting negprot to follow so fast. - * This is a simple solution that works without complicating the code - * and causes no significant slowing down on mount for everyone else + * 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). */ - usleep_range(1000, 2000); + 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 -EIO; + } + 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 */ + rc = -EREMOTEIO; + break; + case RFC1002_UNSPECIFIED_ERROR: + default: + /* other/unknown error */ + rc = -EIO; + break; + } + } else { + cifs_dbg(VFS, "RFC 1002 negative session response\n"); + rc = -EIO; + } + 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 -EIO; + } - return rc; + return 0; } static int @@ -3123,8 +3267,12 @@ generic_ip_connect(struct TCP_Server_Info *server) /* * Grab netns reference for the socket. * - * It'll be released here, on error, or in clean_demultiplex_info() upon server - * teardown. + * This reference will be released in several situations: + * - In the failure path before the cifsd thread is started. + * - In the all place where server->socket is released, it is + * also set to NULL. + * - Ultimately in clean_demultiplex_info(), during the final + * teardown. */ get_net(net); @@ -3140,10 +3288,8 @@ generic_ip_connect(struct TCP_Server_Info *server) } rc = bind_socket(server); - if (rc < 0) { - put_net(cifs_net_ns(server)); + if (rc < 0) return rc; - } /* * Eventually check for other socket options to change from @@ -3189,9 +3335,6 @@ generic_ip_connect(struct TCP_Server_Info *server) if (sport == htons(RFC1001_PORT)) rc = ip_rfc1001_connect(server); - if (rc < 0) - put_net(cifs_net_ns(server)); - return rc; } @@ -3981,7 +4124,7 @@ cifs_setup_session(const unsigned int xid, struct cifs_ses *ses, struct TCP_Server_Info *server, struct nls_table *nls_info) { - int rc = -ENOSYS; + 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; @@ -4033,6 +4176,26 @@ cifs_setup_session(const unsigned int xid, struct cifs_ses *ses, 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); @@ -4045,8 +4208,12 @@ cifs_setup_session(const unsigned int xid, struct cifs_ses *ses, cifs_dbg(FYI, "Security Mode: 0x%x Capabilities: 0x%x TimeAdjust: %d\n", server->sec_mode, server->capabilities, server->timeAdj); - if (server->ops->sess_setup) - rc = server->ops->sess_setup(xid, ses, server, nls_info); + if (!rc) { + if (server->ops->sess_setup) + rc = server->ops->sess_setup(xid, ses, server, nls_info); + else + rc = -ENOSYS; + } if (rc) { cifs_server_dbg(VFS, "Send error in SessSetup = %d\n", rc); @@ -4116,6 +4283,7 @@ cifs_construct_tcon(struct cifs_sb_info *cifs_sb, kuid_t fsuid) 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 = cifs_set_vol_auth(ctx, master_tcon->ses); if (rc) { |