#include "ntlmssp.h"
#include "nterr.h"
#include <linux/utsname.h>
+#include "cifs_spnego.h"
extern void SMBNTencrypt(unsigned char *passwd, unsigned char *c8,
unsigned char *p24);
+/* Checks if this is the first smb session to be reconnected after
+ the socket has been reestablished (so we know whether to use vc 0).
+ Called while holding the cifs_tcp_ses_lock, so do not block */
+static bool is_first_ses_reconnect(struct cifsSesInfo *ses)
+{
+ struct list_head *tmp;
+ struct cifsSesInfo *tmp_ses;
+
+ list_for_each(tmp, &ses->server->smb_ses_list) {
+ tmp_ses = list_entry(tmp, struct cifsSesInfo,
+ smb_ses_list);
+ if (tmp_ses->need_reconnect == false)
+ return false;
+ }
+ /* could not find a session that was already connected,
+ this must be the first one we are reconnecting */
+ return true;
+}
+
+/*
+ * vc number 0 is treated specially by some servers, and should be the
+ * first one we request. After that we can use vcnumbers up to maxvcs,
+ * one for each smb session (some Windows versions set maxvcs incorrectly
+ * so maxvc=1 can be ignored). If we have too many vcs, we can reuse
+ * any vc but zero (some servers reset the connection on vcnum zero)
+ *
+ */
+static __le16 get_next_vcnum(struct cifsSesInfo *ses)
+{
+ __u16 vcnum = 0;
+ struct list_head *tmp;
+ struct cifsSesInfo *tmp_ses;
+ __u16 max_vcs = ses->server->max_vcs;
+ __u16 i;
+ int free_vc_found = 0;
+
+ /* Quoting the MS-SMB specification: "Windows-based SMB servers set this
+ field to one but do not enforce this limit, which allows an SMB client
+ to establish more virtual circuits than allowed by this value ... but
+ other server implementations can enforce this limit." */
+ if (max_vcs < 2)
+ max_vcs = 0xFFFF;
+
+ write_lock(&cifs_tcp_ses_lock);
+ if ((ses->need_reconnect) && is_first_ses_reconnect(ses))
+ goto get_vc_num_exit; /* vcnum will be zero */
+ for (i = ses->server->srv_count - 1; i < max_vcs; i++) {
+ if (i == 0) /* this is the only connection, use vc 0 */
+ break;
+
+ free_vc_found = 1;
+
+ list_for_each(tmp, &ses->server->smb_ses_list) {
+ tmp_ses = list_entry(tmp, struct cifsSesInfo,
+ smb_ses_list);
+ if (tmp_ses->vcnum == i) {
+ free_vc_found = 0;
+ break; /* found duplicate, try next vcnum */
+ }
+ }
+ if (free_vc_found)
+ break; /* we found a vcnumber that will work - use it */
+ }
+
+ if (i == 0)
+ vcnum = 0; /* for most common case, ie if one smb session, use
+ vc zero. Also for case when no free vcnum, zero
+ is safest to send (some clients only send zero) */
+ else if (free_vc_found == 0)
+ vcnum = 1; /* we can not reuse vc=0 safely, since some servers
+ reset all uids on that, but 1 is ok. */
+ else
+ vcnum = i;
+ ses->vcnum = vcnum;
+get_vc_num_exit:
+ write_unlock(&cifs_tcp_ses_lock);
+
+ return le16_to_cpu(vcnum);
+}
+
static __u32 cifs_ssetup_hdr(struct cifsSesInfo *ses, SESSION_SETUP_ANDX *pSMB)
{
__u32 capabilities = 0;
/* init fields common to all four types of SessSetup */
- /* note that header is initialized to zero in header_assemble */
+ /* Note that offsets for first seven fields in req struct are same */
+ /* in CIFS Specs so does not matter which of 3 forms of struct */
+ /* that we use in next few lines */
+ /* Note that header is initialized to zero in header_assemble */
pSMB->req.AndXCommand = 0xFF;
pSMB->req.MaxBufferSize = cpu_to_le16(ses->server->maxBuf);
pSMB->req.MaxMpxCount = cpu_to_le16(ses->server->maxReq);
+ pSMB->req.VcNumber = get_next_vcnum(ses);
/* Now no need to set SMBFLG_CASELESS or obsolete CANONICAL PATH */
if (ses->capabilities & CAP_UNIX)
capabilities |= CAP_UNIX;
- /* BB check whether to init vcnum BB */
return capabilities;
}
kfree(ses->serverOS);
/* UTF-8 string will not grow more than four times as big as UCS-16 */
- ses->serverOS = kzalloc(4 * len, GFP_KERNEL);
+ ses->serverOS = kzalloc((4 * len) + 2 /* trailing null */, GFP_KERNEL);
if (ses->serverOS != NULL)
cifs_strfromUCS_le(ses->serverOS, (__le16 *)data, len, nls_cp);
data += 2 * (len + 1);
return rc;
kfree(ses->serverNOS);
- ses->serverNOS = kzalloc(4 * len, GFP_KERNEL); /* BB this is wrong length FIXME BB */
+ ses->serverNOS = kzalloc((4 * len) + 2 /* trailing null */, GFP_KERNEL);
if (ses->serverNOS != NULL) {
cifs_strfromUCS_le(ses->serverNOS, (__le16 *)data, len,
nls_cp);
SESSION_SETUP_ANDX *pSMB;
__u32 capabilities;
int count;
- int resp_buf_type = 0;
- struct kvec iov[2];
+ int resp_buf_type;
+ struct kvec iov[3];
enum securityEnum type;
__u16 action;
int bytes_remaining;
+ struct key *spnego_key = NULL;
if (ses == NULL)
return -EINVAL;
capabilities = cifs_ssetup_hdr(ses, pSMB);
- /* we will send the SMB in two pieces,
- a fixed length beginning part, and a
- second part which will include the strings
- and rest of bcc area, in order to avoid having
- to do a large buffer 17K allocation */
+ /* we will send the SMB in three pieces:
+ a fixed length beginning part, an optional
+ SPNEGO blob (which can be zero length), and a
+ last part which will include the strings
+ and rest of bcc area. This allows us to avoid
+ a large buffer 17K allocation */
iov[0].iov_base = (char *)pSMB;
iov[0].iov_len = smb_buf->smb_buf_length + 4;
+ /* setting this here allows the code at the end of the function
+ to free the request buffer if there's an error */
+ resp_buf_type = CIFS_SMALL_BUFFER;
+
/* 2000 big enough to fit max user, domain, NOS name etc. */
str_area = kmalloc(2000, GFP_KERNEL);
if (str_area == NULL) {
- cifs_small_buf_release(smb_buf);
- return -ENOMEM;
+ rc = -ENOMEM;
+ goto ssetup_exit;
}
bcc_ptr = str_area;
ses->flags &= ~CIFS_SES_LANMAN;
+ iov[1].iov_base = NULL;
+ iov[1].iov_len = 0;
+
if (type == LANMAN) {
#ifdef CONFIG_CIFS_WEAK_PW_HASH
char lnm_session_key[CIFS_SESS_KEY_SIZE];
+ pSMB->req.hdr.Flags2 &= ~SMBFLG2_UNICODE;
+
/* no capabilities flags in old lanman negotiation */
pSMB->old_req.PasswordLength = cpu_to_le16(CIFS_SESS_KEY_SIZE);
/* BB calculate hash with password */
/* and copy into bcc */
- calc_lanman_hash(ses, lnm_session_key);
+ calc_lanman_hash(ses->password, ses->server->cryptKey,
+ ses->server->secMode & SECMODE_PW_ENCRYPT ?
+ true : false, lnm_session_key);
+
ses->flags |= CIFS_SES_LANMAN;
-/* #ifdef CONFIG_CIFS_DEBUG2
- cifs_dump_mem("cryptkey: ",ses->server->cryptKey,
- CIFS_SESS_KEY_SIZE);
-#endif */
memcpy(bcc_ptr, (char *)lnm_session_key, CIFS_SESS_KEY_SIZE);
bcc_ptr += CIFS_SESS_KEY_SIZE;
struct ntlmv2_resp */
if (v2_sess_key == NULL) {
- cifs_small_buf_release(smb_buf);
- return -ENOMEM;
+ rc = -ENOMEM;
+ goto ssetup_exit;
}
pSMB->req_no_secext.Capabilities = cpu_to_le32(capabilities);
unicode_ssetup_strings(&bcc_ptr, ses, nls_cp);
} else
ascii_ssetup_strings(&bcc_ptr, ses, nls_cp);
- } else /* NTLMSSP or SPNEGO */ {
+ } else if (type == Kerberos || type == MSKerberos) {
+#ifdef CONFIG_CIFS_UPCALL
+ struct cifs_spnego_msg *msg;
+ spnego_key = cifs_get_spnego_key(ses);
+ if (IS_ERR(spnego_key)) {
+ rc = PTR_ERR(spnego_key);
+ spnego_key = NULL;
+ goto ssetup_exit;
+ }
+
+ msg = spnego_key->payload.data;
+ /* check version field to make sure that cifs.upcall is
+ sending us a response in an expected form */
+ if (msg->version != CIFS_SPNEGO_UPCALL_VERSION) {
+ cERROR(1, ("incorrect version of cifs.upcall (expected"
+ " %d but got %d)",
+ CIFS_SPNEGO_UPCALL_VERSION, msg->version));
+ rc = -EKEYREJECTED;
+ goto ssetup_exit;
+ }
+ /* bail out if key is too long */
+ if (msg->sesskey_len >
+ sizeof(ses->server->mac_signing_key.data.krb5)) {
+ cERROR(1, ("Kerberos signing key too long (%u bytes)",
+ msg->sesskey_len));
+ rc = -EOVERFLOW;
+ goto ssetup_exit;
+ }
+ if (first_time) {
+ ses->server->mac_signing_key.len = msg->sesskey_len;
+ memcpy(ses->server->mac_signing_key.data.krb5,
+ msg->data, msg->sesskey_len);
+ }
pSMB->req.hdr.Flags2 |= SMBFLG2_EXT_SEC;
capabilities |= CAP_EXTENDED_SECURITY;
pSMB->req.Capabilities = cpu_to_le32(capabilities);
- /* BB set password lengths */
+ iov[1].iov_base = msg->data + msg->sesskey_len;
+ iov[1].iov_len = msg->secblob_len;
+ pSMB->req.SecurityBlobLength = cpu_to_le16(iov[1].iov_len);
+
+ if (ses->capabilities & CAP_UNICODE) {
+ /* unicode strings must be word aligned */
+ if ((iov[0].iov_len + iov[1].iov_len) % 2) {
+ *bcc_ptr = 0;
+ bcc_ptr++;
+ }
+ unicode_oslm_strings(&bcc_ptr, nls_cp);
+ unicode_domain_string(&bcc_ptr, ses, nls_cp);
+ } else
+ /* BB: is this right? */
+ ascii_ssetup_strings(&bcc_ptr, ses, nls_cp);
+#else /* ! CONFIG_CIFS_UPCALL */
+ cERROR(1, ("Kerberos negotiated but upcall support disabled!"));
+ rc = -ENOSYS;
+ goto ssetup_exit;
+#endif /* CONFIG_CIFS_UPCALL */
+ } else {
+ cERROR(1, ("secType %d not supported!", type));
+ rc = -ENOSYS;
+ goto ssetup_exit;
}
- count = (long) bcc_ptr - (long) str_area;
+ iov[2].iov_base = str_area;
+ iov[2].iov_len = (long) bcc_ptr - (long) str_area;
+
+ count = iov[1].iov_len + iov[2].iov_len;
smb_buf->smb_buf_length += count;
BCC_LE(smb_buf) = cpu_to_le16(count);
- iov[1].iov_base = str_area;
- iov[1].iov_len = count;
- rc = SendReceive2(xid, ses, iov, 2 /* num_iovecs */, &resp_buf_type,
+ rc = SendReceive2(xid, ses, iov, 3 /* num_iovecs */, &resp_buf_type,
CIFS_STD_OP /* not long */ | CIFS_LOG_ERROR);
/* SMB request buf freed in SendReceive2 */
ses, nls_cp);
ssetup_exit:
+ if (spnego_key) {
+ key_revoke(spnego_key);
+ key_put(spnego_key);
+ }
kfree(str_area);
if (resp_buf_type == CIFS_SMALL_BUFFER) {
cFYI(1, ("ssetup freeing small buf %p", iov[0].iov_base));