diff mbox

[1/2] SMB2: Separate Kerberos authentication from SMB2_sess_setup

Message ID 1474553094-8884-2-git-send-email-sprabhu@redhat.com (mailing list archive)
State New, archived
Headers show

Commit Message

Sachin Prabhu Sept. 22, 2016, 2:04 p.m. UTC
Add helper functions and split Kerberos authentication off
SMB2_sess_setup.

Signed-off-by: Sachin Prabhu <sprabhu@redhat.com>
---
 fs/cifs/smb2pdu.c | 270 ++++++++++++++++++++++++++++++++++++++++++++----------
 1 file changed, 224 insertions(+), 46 deletions(-)

Comments

Steve French Oct. 6, 2016, 9:02 p.m. UTC | #1
Shouldn't the

#ifdef CONFIG_CIFS_UPCALL
before SMB2_auth_kerberos(struct SMB2_sess_data *sess_data) be moved
up (before SMB2_sess_esablish_session)?

On Thu, Sep 22, 2016 at 9:04 AM, Sachin Prabhu <sprabhu@redhat.com> wrote:
> Add helper functions and split Kerberos authentication off
> SMB2_sess_setup.
>
> Signed-off-by: Sachin Prabhu <sprabhu@redhat.com>
> ---
>  fs/cifs/smb2pdu.c | 270 ++++++++++++++++++++++++++++++++++++++++++++----------
>  1 file changed, 224 insertions(+), 46 deletions(-)
>
> diff --git a/fs/cifs/smb2pdu.c b/fs/cifs/smb2pdu.c
> index 29e06db..3cfdb6b 100644
> --- a/fs/cifs/smb2pdu.c
> +++ b/fs/cifs/smb2pdu.c
> @@ -574,6 +574,211 @@ vneg_out:
>         return -EIO;
>  }
>
> +struct SMB2_sess_data {
> +       unsigned int xid;
> +       struct cifs_ses *ses;
> +       struct nls_table *nls_cp;
> +       void (*func)(struct SMB2_sess_data *);
> +       int result;
> +
> +       /* 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
> +        */
> +       int buf0_type;
> +       struct kvec iov[2];
> +};
> +
> +static int
> +SMB2_sess_alloc_buffer(struct SMB2_sess_data *sess_data)
> +{
> +       int rc;
> +       struct cifs_ses *ses = sess_data->ses;
> +       struct smb2_sess_setup_req *req;
> +       struct TCP_Server_Info *server = ses->server;
> +
> +       rc = small_smb2_init(SMB2_SESSION_SETUP, NULL, (void **) &req);
> +       if (rc)
> +               return rc;
> +
> +       req->hdr.SessionId = 0; /* First session, not a reauthenticate */
> +       req->Flags = 0; /* MBZ */
> +       /* to enable echos and oplocks */
> +       req->hdr.CreditRequest = cpu_to_le16(3);
> +
> +       /* only one of SMB2 signing flags may be set in SMB2 request */
> +       if (server->sign)
> +               req->SecurityMode = SMB2_NEGOTIATE_SIGNING_REQUIRED;
> +       else if (global_secflags & CIFSSEC_MAY_SIGN) /* one flag unlike MUST_ */
> +               req->SecurityMode = SMB2_NEGOTIATE_SIGNING_ENABLED;
> +       else
> +               req->SecurityMode = 0;
> +
> +       req->Capabilities = 0;
> +       req->Channel = 0; /* MBZ */
> +
> +       sess_data->iov[0].iov_base = (char *)req;
> +       /* 4 for rfc1002 length field and 1 for pad */
> +       sess_data->iov[0].iov_len = get_rfc1002_length(req) + 4 - 1;
> +       /*
> +        * This variable will be used to clear the buffer
> +        * allocated above in case of any error in the calling function.
> +        */
> +       sess_data->buf0_type = CIFS_SMALL_BUFFER;
> +
> +       return 0;
> +}
> +
> +static void
> +SMB2_sess_free_buffer(struct SMB2_sess_data *sess_data)
> +{
> +       free_rsp_buf(sess_data->buf0_type, sess_data->iov[0].iov_base);
> +       sess_data->buf0_type = CIFS_NO_BUFFER;
> +}
> +
> +static int
> +SMB2_sess_sendreceive(struct SMB2_sess_data *sess_data)
> +{
> +       int rc;
> +       struct smb2_sess_setup_req *req = sess_data->iov[0].iov_base;
> +
> +       /* Testing shows that buffer offset must be at location of Buffer[0] */
> +       req->SecurityBufferOffset =
> +               cpu_to_le16(sizeof(struct smb2_sess_setup_req) -
> +                       1 /* pad */ - 4 /* rfc1001 len */);
> +       req->SecurityBufferLength = cpu_to_le16(sess_data->iov[1].iov_len);
> +
> +       inc_rfc1001_len(req, sess_data->iov[1].iov_len - 1 /* pad */);
> +
> +       /* BB add code to build os and lm fields */
> +
> +       rc = SendReceive2(sess_data->xid, sess_data->ses,
> +                               sess_data->iov, 2,
> +                               &sess_data->buf0_type,
> +                               CIFS_LOG_ERROR | CIFS_NEG_OP);
> +
> +       return rc;
> +}
> +
> +static int
> +SMB2_sess_establish_session(struct SMB2_sess_data *sess_data)
> +{
> +       int rc = 0;
> +       struct cifs_ses *ses = sess_data->ses;
> +
> +       mutex_lock(&ses->server->srv_mutex);
> +       if (ses->server->sign && ses->server->ops->generate_signingkey) {
> +               rc = ses->server->ops->generate_signingkey(ses);
> +               kfree(ses->auth_key.response);
> +               ses->auth_key.response = NULL;
> +               if (rc) {
> +                       cifs_dbg(FYI,
> +                               "SMB3 session key generation failed\n");
> +                       mutex_unlock(&ses->server->srv_mutex);
> +                       goto keygen_exit;
> +               }
> +       }
> +       if (!ses->server->session_estab) {
> +               ses->server->sequence_number = 0x2;
> +               ses->server->session_estab = true;
> +       }
> +       mutex_unlock(&ses->server->srv_mutex);
> +
> +       cifs_dbg(FYI, "SMB2/3 session established successfully\n");
> +       spin_lock(&GlobalMid_Lock);
> +       ses->status = CifsGood;
> +       ses->need_reconnect = false;
> +       spin_unlock(&GlobalMid_Lock);
> +
> +keygen_exit:
> +       if (!ses->server->sign) {
> +               kfree(ses->auth_key.response);
> +               ses->auth_key.response = NULL;
> +       }
> +       return rc;
> +}
> +
> +#ifdef CONFIG_CIFS_UPCALL
> +static void
> +SMB2_auth_kerberos(struct SMB2_sess_data *sess_data)
> +{
> +       int rc;
> +       struct cifs_ses *ses = sess_data->ses;
> +       struct cifs_spnego_msg *msg;
> +       struct key *spnego_key = NULL;
> +       struct smb2_sess_setup_rsp *rsp = NULL;
> +
> +       rc = SMB2_sess_alloc_buffer(sess_data);
> +       if (rc)
> +               goto out;
> +
> +       spnego_key = cifs_get_spnego_key(ses);
> +       if (IS_ERR(spnego_key)) {
> +               rc = PTR_ERR(spnego_key);
> +               spnego_key = NULL;
> +               goto out;
> +       }
> +
> +       msg = spnego_key->payload.data[0];
> +       /*
> +        * 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) {
> +               cifs_dbg(VFS,
> +                         "bad cifs.upcall version. Expected %d got %d",
> +                         CIFS_SPNEGO_UPCALL_VERSION, msg->version);
> +               rc = -EKEYREJECTED;
> +               goto out_put_spnego_key;
> +       }
> +
> +       ses->auth_key.response = kmemdup(msg->data, msg->sesskey_len,
> +                                        GFP_KERNEL);
> +       if (!ses->auth_key.response) {
> +               cifs_dbg(VFS,
> +                       "Kerberos can't allocate (%u bytes) memory",
> +                       msg->sesskey_len);
> +               rc = -ENOMEM;
> +               goto out_put_spnego_key;
> +       }
> +       ses->auth_key.len = msg->sesskey_len;
> +
> +       sess_data->iov[1].iov_base = msg->data + msg->sesskey_len;
> +       sess_data->iov[1].iov_len = msg->secblob_len;
> +
> +       rc = SMB2_sess_sendreceive(sess_data);
> +       if (rc)
> +               goto out_put_spnego_key;
> +
> +       rsp = (struct smb2_sess_setup_rsp *)sess_data->iov[0].iov_base;
> +       ses->Suid = rsp->hdr.SessionId;
> +
> +       ses->session_flags = le16_to_cpu(rsp->SessionFlags);
> +       if (ses->session_flags & SMB2_SESSION_FLAG_ENCRYPT_DATA)
> +               cifs_dbg(VFS, "SMB3 encryption not supported yet\n");
> +
> +       rc = SMB2_sess_establish_session(sess_data);
> +out_put_spnego_key:
> +       key_invalidate(spnego_key);
> +       key_put(spnego_key);
> +out:
> +       sess_data->result = rc;
> +       sess_data->func = NULL;
> +       SMB2_sess_free_buffer(sess_data);
> +}
> +#else
> +static void
> +SMB2_auth_kerberos(struct SMB2_sess_data *sess_data)
> +{
> +       cifs_dbg(VFS, "Kerberos negotiated but upcall support disabled!\n");
> +       sess_data->result = -EOPNOTSUPP;
> +       sess_data->func = NULL;
> +}
> +#endif
> +
>  int
>  SMB2_sess_setup(const unsigned int xid, struct cifs_ses *ses,
>                 const struct nls_table *nls_cp)
> @@ -586,10 +791,10 @@ SMB2_sess_setup(const unsigned int xid, struct cifs_ses *ses,
>         __le32 phase = NtLmNegotiate; /* NTLMSSP, if needed, is multistage */
>         struct TCP_Server_Info *server = ses->server;
>         u16 blob_length = 0;
> -       struct key *spnego_key = NULL;
>         char *security_blob = NULL;
>         unsigned char *ntlmssp_blob = NULL;
>         bool use_spnego = false; /* else use raw ntlmssp */
> +       struct SMB2_sess_data *sess_data;
>
>         cifs_dbg(FYI, "Session Setup\n");
>
> @@ -598,6 +803,19 @@ SMB2_sess_setup(const unsigned int xid, struct cifs_ses *ses,
>                 return -EIO;
>         }
>
> +       sess_data = kzalloc(sizeof(struct SMB2_sess_data), GFP_KERNEL);
> +       if (!sess_data)
> +               return -ENOMEM;
> +       sess_data->xid = xid;
> +       sess_data->ses = ses;
> +       sess_data->buf0_type = CIFS_NO_BUFFER;
> +       sess_data->nls_cp = (struct nls_table *) nls_cp;
> +
> +       if (ses->sectype == Kerberos) {
> +               SMB2_auth_kerberos(sess_data);
> +               goto out;
> +       }
> +
>         /*
>          * If we are here due to reconnect, free per-smb session key
>          * in case signing was required.
> @@ -646,47 +864,7 @@ ssetup_ntlmssp_authenticate:
>         /* 4 for rfc1002 length field and 1 for pad */
>         iov[0].iov_len = get_rfc1002_length(req) + 4 - 1;
>
> -       if (ses->sectype == Kerberos) {
> -#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[0];
> -               /*
> -                * 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) {
> -                       cifs_dbg(VFS,
> -                                 "bad cifs.upcall version. Expected %d got %d",
> -                                 CIFS_SPNEGO_UPCALL_VERSION, msg->version);
> -                       rc = -EKEYREJECTED;
> -                       goto ssetup_exit;
> -               }
> -               ses->auth_key.response = kmemdup(msg->data, msg->sesskey_len,
> -                                                GFP_KERNEL);
> -               if (!ses->auth_key.response) {
> -                       cifs_dbg(VFS,
> -                               "Kerberos can't allocate (%u bytes) memory",
> -                               msg->sesskey_len);
> -                       rc = -ENOMEM;
> -                       goto ssetup_exit;
> -               }
> -               ses->auth_key.len = msg->sesskey_len;
> -               blob_length = msg->secblob_len;
> -               iov[1].iov_base = msg->data + msg->sesskey_len;
> -               iov[1].iov_len = blob_length;
> -#else
> -               rc = -EOPNOTSUPP;
> -               goto ssetup_exit;
> -#endif /* CONFIG_CIFS_UPCALL */
> -       } else if (phase == NtLmNegotiate) { /* if not krb5 must be ntlmssp */
> +       if (phase == NtLmNegotiate) {
>                 ntlmssp_blob = kmalloc(sizeof(struct _NEGOTIATE_MESSAGE),
>                                        GFP_KERNEL);
>                 if (ntlmssp_blob == NULL) {
> @@ -829,13 +1007,13 @@ keygen_exit:
>                 kfree(ses->auth_key.response);
>                 ses->auth_key.response = NULL;
>         }
> -       if (spnego_key) {
> -               key_invalidate(spnego_key);
> -               key_put(spnego_key);
> -       }
>         kfree(ses->ntlmssp);
>
>         return rc;
> +out:
> +       rc = sess_data->result;
> +       kfree(sess_data);
> +       return rc;
>  }
>
>  int
> --
> 2.7.4
>
> --
> To unsubscribe from this list: send the line "unsubscribe linux-cifs" in
> the body of a message to majordomo@vger.kernel.org
> More majordomo info at  http://vger.kernel.org/majordomo-info.html
Sachin Prabhu Oct. 7, 2016, 1:42 p.m. UTC | #2
On Thu, 2016-10-06 at 16:02 -0500, Steve French wrote:
> Shouldn't the
> 
> #ifdef CONFIG_CIFS_UPCALL
> before SMB2_auth_kerberos(struct SMB2_sess_data *sess_data) be moved
> up (before SMB2_sess_esablish_session)?
> 

No. This helper function is intended to also be used by the RawNTLMSSP
authentication method which the subsequent patch introduces.

The patch 
fab27e8c595a2028c71366fbb9fb2dada00ef7e7
Set previous session id correctly on SMB3 reconnect
changes a bit of the code which these patches change. I am rebasing the
two patches on top of the for-next branch and posting it to the list as
soon as I've completed my testing again.

Sachin Prabhu

> On Thu, Sep 22, 2016 at 9:04 AM, Sachin Prabhu <sprabhu@redhat.com>
> wrote:
> > 
> > Add helper functions and split Kerberos authentication off
> > SMB2_sess_setup.
> > 
> > Signed-off-by: Sachin Prabhu <sprabhu@redhat.com>
> > ---
> >  fs/cifs/smb2pdu.c | 270
> > ++++++++++++++++++++++++++++++++++++++++++++----------
> >  1 file changed, 224 insertions(+), 46 deletions(-)
> > 
> > diff --git a/fs/cifs/smb2pdu.c b/fs/cifs/smb2pdu.c
> > index 29e06db..3cfdb6b 100644
> > --- a/fs/cifs/smb2pdu.c
> > +++ b/fs/cifs/smb2pdu.c
> > @@ -574,6 +574,211 @@ vneg_out:
> >         return -EIO;
> >  }
> > 
> > +struct SMB2_sess_data {
> > +       unsigned int xid;
> > +       struct cifs_ses *ses;
> > +       struct nls_table *nls_cp;
> > +       void (*func)(struct SMB2_sess_data *);
> > +       int result;
> > +
> > +       /* 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
> > +        */
> > +       int buf0_type;
> > +       struct kvec iov[2];
> > +};
> > +
> > +static int
> > +SMB2_sess_alloc_buffer(struct SMB2_sess_data *sess_data)
> > +{
> > +       int rc;
> > +       struct cifs_ses *ses = sess_data->ses;
> > +       struct smb2_sess_setup_req *req;
> > +       struct TCP_Server_Info *server = ses->server;
> > +
> > +       rc = small_smb2_init(SMB2_SESSION_SETUP, NULL, (void **)
> > &req);
> > +       if (rc)
> > +               return rc;
> > +
> > +       req->hdr.SessionId = 0; /* First session, not a
> > reauthenticate */
> > +       req->Flags = 0; /* MBZ */
> > +       /* to enable echos and oplocks */
> > +       req->hdr.CreditRequest = cpu_to_le16(3);
> > +
> > +       /* only one of SMB2 signing flags may be set in SMB2
> > request */
> > +       if (server->sign)
> > +               req->SecurityMode =
> > SMB2_NEGOTIATE_SIGNING_REQUIRED;
> > +       else if (global_secflags & CIFSSEC_MAY_SIGN) /* one flag
> > unlike MUST_ */
> > +               req->SecurityMode = SMB2_NEGOTIATE_SIGNING_ENABLED;
> > +       else
> > +               req->SecurityMode = 0;
> > +
> > +       req->Capabilities = 0;
> > +       req->Channel = 0; /* MBZ */
> > +
> > +       sess_data->iov[0].iov_base = (char *)req;
> > +       /* 4 for rfc1002 length field and 1 for pad */
> > +       sess_data->iov[0].iov_len = get_rfc1002_length(req) + 4 -
> > 1;
> > +       /*
> > +        * This variable will be used to clear the buffer
> > +        * allocated above in case of any error in the calling
> > function.
> > +        */
> > +       sess_data->buf0_type = CIFS_SMALL_BUFFER;
> > +
> > +       return 0;
> > +}
> > +
> > +static void
> > +SMB2_sess_free_buffer(struct SMB2_sess_data *sess_data)
> > +{
> > +       free_rsp_buf(sess_data->buf0_type, sess_data-
> > >iov[0].iov_base);
> > +       sess_data->buf0_type = CIFS_NO_BUFFER;
> > +}
> > +
> > +static int
> > +SMB2_sess_sendreceive(struct SMB2_sess_data *sess_data)
> > +{
> > +       int rc;
> > +       struct smb2_sess_setup_req *req = sess_data-
> > >iov[0].iov_base;
> > +
> > +       /* Testing shows that buffer offset must be at location of
> > Buffer[0] */
> > +       req->SecurityBufferOffset =
> > +               cpu_to_le16(sizeof(struct smb2_sess_setup_req) -
> > +                       1 /* pad */ - 4 /* rfc1001 len */);
> > +       req->SecurityBufferLength = cpu_to_le16(sess_data-
> > >iov[1].iov_len);
> > +
> > +       inc_rfc1001_len(req, sess_data->iov[1].iov_len - 1 /* pad
> > */);
> > +
> > +       /* BB add code to build os and lm fields */
> > +
> > +       rc = SendReceive2(sess_data->xid, sess_data->ses,
> > +                               sess_data->iov, 2,
> > +                               &sess_data->buf0_type,
> > +                               CIFS_LOG_ERROR | CIFS_NEG_OP);
> > +
> > +       return rc;
> > +}
> > +
> > +static int
> > +SMB2_sess_establish_session(struct SMB2_sess_data *sess_data)
> > +{
> > +       int rc = 0;
> > +       struct cifs_ses *ses = sess_data->ses;
> > +
> > +       mutex_lock(&ses->server->srv_mutex);
> > +       if (ses->server->sign && ses->server->ops-
> > >generate_signingkey) {
> > +               rc = ses->server->ops->generate_signingkey(ses);
> > +               kfree(ses->auth_key.response);
> > +               ses->auth_key.response = NULL;
> > +               if (rc) {
> > +                       cifs_dbg(FYI,
> > +                               "SMB3 session key generation
> > failed\n");
> > +                       mutex_unlock(&ses->server->srv_mutex);
> > +                       goto keygen_exit;
> > +               }
> > +       }
> > +       if (!ses->server->session_estab) {
> > +               ses->server->sequence_number = 0x2;
> > +               ses->server->session_estab = true;
> > +       }
> > +       mutex_unlock(&ses->server->srv_mutex);
> > +
> > +       cifs_dbg(FYI, "SMB2/3 session established successfully\n");
> > +       spin_lock(&GlobalMid_Lock);
> > +       ses->status = CifsGood;
> > +       ses->need_reconnect = false;
> > +       spin_unlock(&GlobalMid_Lock);
> > +
> > +keygen_exit:
> > +       if (!ses->server->sign) {
> > +               kfree(ses->auth_key.response);
> > +               ses->auth_key.response = NULL;
> > +       }
> > +       return rc;
> > +}
> > +
> > +#ifdef CONFIG_CIFS_UPCALL
> > +static void
> > +SMB2_auth_kerberos(struct SMB2_sess_data *sess_data)
> > +{
> > +       int rc;
> > +       struct cifs_ses *ses = sess_data->ses;
> > +       struct cifs_spnego_msg *msg;
> > +       struct key *spnego_key = NULL;
> > +       struct smb2_sess_setup_rsp *rsp = NULL;
> > +
> > +       rc = SMB2_sess_alloc_buffer(sess_data);
> > +       if (rc)
> > +               goto out;
> > +
> > +       spnego_key = cifs_get_spnego_key(ses);
> > +       if (IS_ERR(spnego_key)) {
> > +               rc = PTR_ERR(spnego_key);
> > +               spnego_key = NULL;
> > +               goto out;
> > +       }
> > +
> > +       msg = spnego_key->payload.data[0];
> > +       /*
> > +        * 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) {
> > +               cifs_dbg(VFS,
> > +                         "bad cifs.upcall version. Expected %d got
> > %d",
> > +                         CIFS_SPNEGO_UPCALL_VERSION, msg-
> > >version);
> > +               rc = -EKEYREJECTED;
> > +               goto out_put_spnego_key;
> > +       }
> > +
> > +       ses->auth_key.response = kmemdup(msg->data, msg-
> > >sesskey_len,
> > +                                        GFP_KERNEL);
> > +       if (!ses->auth_key.response) {
> > +               cifs_dbg(VFS,
> > +                       "Kerberos can't allocate (%u bytes)
> > memory",
> > +                       msg->sesskey_len);
> > +               rc = -ENOMEM;
> > +               goto out_put_spnego_key;
> > +       }
> > +       ses->auth_key.len = msg->sesskey_len;
> > +
> > +       sess_data->iov[1].iov_base = msg->data + msg->sesskey_len;
> > +       sess_data->iov[1].iov_len = msg->secblob_len;
> > +
> > +       rc = SMB2_sess_sendreceive(sess_data);
> > +       if (rc)
> > +               goto out_put_spnego_key;
> > +
> > +       rsp = (struct smb2_sess_setup_rsp *)sess_data-
> > >iov[0].iov_base;
> > +       ses->Suid = rsp->hdr.SessionId;
> > +
> > +       ses->session_flags = le16_to_cpu(rsp->SessionFlags);
> > +       if (ses->session_flags & SMB2_SESSION_FLAG_ENCRYPT_DATA)
> > +               cifs_dbg(VFS, "SMB3 encryption not supported
> > yet\n");
> > +
> > +       rc = SMB2_sess_establish_session(sess_data);
> > +out_put_spnego_key:
> > +       key_invalidate(spnego_key);
> > +       key_put(spnego_key);
> > +out:
> > +       sess_data->result = rc;
> > +       sess_data->func = NULL;
> > +       SMB2_sess_free_buffer(sess_data);
> > +}
> > +#else
> > +static void
> > +SMB2_auth_kerberos(struct SMB2_sess_data *sess_data)
> > +{
> > +       cifs_dbg(VFS, "Kerberos negotiated but upcall support
> > disabled!\n");
> > +       sess_data->result = -EOPNOTSUPP;
> > +       sess_data->func = NULL;
> > +}
> > +#endif
> > +
> >  int
> >  SMB2_sess_setup(const unsigned int xid, struct cifs_ses *ses,
> >                 const struct nls_table *nls_cp)
> > @@ -586,10 +791,10 @@ SMB2_sess_setup(const unsigned int xid,
> > struct cifs_ses *ses,
> >         __le32 phase = NtLmNegotiate; /* NTLMSSP, if needed, is
> > multistage */
> >         struct TCP_Server_Info *server = ses->server;
> >         u16 blob_length = 0;
> > -       struct key *spnego_key = NULL;
> >         char *security_blob = NULL;
> >         unsigned char *ntlmssp_blob = NULL;
> >         bool use_spnego = false; /* else use raw ntlmssp */
> > +       struct SMB2_sess_data *sess_data;
> > 
> >         cifs_dbg(FYI, "Session Setup\n");
> > 
> > @@ -598,6 +803,19 @@ SMB2_sess_setup(const unsigned int xid, struct
> > cifs_ses *ses,
> >                 return -EIO;
> >         }
> > 
> > +       sess_data = kzalloc(sizeof(struct SMB2_sess_data),
> > GFP_KERNEL);
> > +       if (!sess_data)
> > +               return -ENOMEM;
> > +       sess_data->xid = xid;
> > +       sess_data->ses = ses;
> > +       sess_data->buf0_type = CIFS_NO_BUFFER;
> > +       sess_data->nls_cp = (struct nls_table *) nls_cp;
> > +
> > +       if (ses->sectype == Kerberos) {
> > +               SMB2_auth_kerberos(sess_data);
> > +               goto out;
> > +       }
> > +
> >         /*
> >          * If we are here due to reconnect, free per-smb session
> > key
> >          * in case signing was required.
> > @@ -646,47 +864,7 @@ ssetup_ntlmssp_authenticate:
> >         /* 4 for rfc1002 length field and 1 for pad */
> >         iov[0].iov_len = get_rfc1002_length(req) + 4 - 1;
> > 
> > -       if (ses->sectype == Kerberos) {
> > -#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[0];
> > -               /*
> > -                * 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) {
> > -                       cifs_dbg(VFS,
> > -                                 "bad cifs.upcall version.
> > Expected %d got %d",
> > -                                 CIFS_SPNEGO_UPCALL_VERSION, msg-
> > >version);
> > -                       rc = -EKEYREJECTED;
> > -                       goto ssetup_exit;
> > -               }
> > -               ses->auth_key.response = kmemdup(msg->data, msg-
> > >sesskey_len,
> > -                                                GFP_KERNEL);
> > -               if (!ses->auth_key.response) {
> > -                       cifs_dbg(VFS,
> > -                               "Kerberos can't allocate (%u bytes)
> > memory",
> > -                               msg->sesskey_len);
> > -                       rc = -ENOMEM;
> > -                       goto ssetup_exit;
> > -               }
> > -               ses->auth_key.len = msg->sesskey_len;
> > -               blob_length = msg->secblob_len;
> > -               iov[1].iov_base = msg->data + msg->sesskey_len;
> > -               iov[1].iov_len = blob_length;
> > -#else
> > -               rc = -EOPNOTSUPP;
> > -               goto ssetup_exit;
> > -#endif /* CONFIG_CIFS_UPCALL */
> > -       } else if (phase == NtLmNegotiate) { /* if not krb5 must be
> > ntlmssp */
> > +       if (phase == NtLmNegotiate) {
> >                 ntlmssp_blob = kmalloc(sizeof(struct
> > _NEGOTIATE_MESSAGE),
> >                                        GFP_KERNEL);
> >                 if (ntlmssp_blob == NULL) {
> > @@ -829,13 +1007,13 @@ keygen_exit:
> >                 kfree(ses->auth_key.response);
> >                 ses->auth_key.response = NULL;
> >         }
> > -       if (spnego_key) {
> > -               key_invalidate(spnego_key);
> > -               key_put(spnego_key);
> > -       }
> >         kfree(ses->ntlmssp);
> > 
> >         return rc;
> > +out:
> > +       rc = sess_data->result;
> > +       kfree(sess_data);
> > +       return rc;
> >  }
> > 
> >  int
> > --
> > 2.7.4
> > 
> > --
> > To unsubscribe from this list: send the line "unsubscribe linux-
> > cifs" in
> > the body of a message to majordomo@vger.kernel.org
> > More majordomo info at  http://vger.kernel.org/majordomo-info.html
> 
> 
> 

--
To unsubscribe from this list: send the line "unsubscribe linux-cifs" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
diff mbox

Patch

diff --git a/fs/cifs/smb2pdu.c b/fs/cifs/smb2pdu.c
index 29e06db..3cfdb6b 100644
--- a/fs/cifs/smb2pdu.c
+++ b/fs/cifs/smb2pdu.c
@@ -574,6 +574,211 @@  vneg_out:
 	return -EIO;
 }
 
+struct SMB2_sess_data {
+	unsigned int xid;
+	struct cifs_ses *ses;
+	struct nls_table *nls_cp;
+	void (*func)(struct SMB2_sess_data *);
+	int result;
+
+	/* 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
+	 */
+	int buf0_type;
+	struct kvec iov[2];
+};
+
+static int
+SMB2_sess_alloc_buffer(struct SMB2_sess_data *sess_data)
+{
+	int rc;
+	struct cifs_ses *ses = sess_data->ses;
+	struct smb2_sess_setup_req *req;
+	struct TCP_Server_Info *server = ses->server;
+
+	rc = small_smb2_init(SMB2_SESSION_SETUP, NULL, (void **) &req);
+	if (rc)
+		return rc;
+
+	req->hdr.SessionId = 0; /* First session, not a reauthenticate */
+	req->Flags = 0; /* MBZ */
+	/* to enable echos and oplocks */
+	req->hdr.CreditRequest = cpu_to_le16(3);
+
+	/* only one of SMB2 signing flags may be set in SMB2 request */
+	if (server->sign)
+		req->SecurityMode = SMB2_NEGOTIATE_SIGNING_REQUIRED;
+	else if (global_secflags & CIFSSEC_MAY_SIGN) /* one flag unlike MUST_ */
+		req->SecurityMode = SMB2_NEGOTIATE_SIGNING_ENABLED;
+	else
+		req->SecurityMode = 0;
+
+	req->Capabilities = 0;
+	req->Channel = 0; /* MBZ */
+
+	sess_data->iov[0].iov_base = (char *)req;
+	/* 4 for rfc1002 length field and 1 for pad */
+	sess_data->iov[0].iov_len = get_rfc1002_length(req) + 4 - 1;
+	/*
+	 * This variable will be used to clear the buffer
+	 * allocated above in case of any error in the calling function.
+	 */
+	sess_data->buf0_type = CIFS_SMALL_BUFFER;
+
+	return 0;
+}
+
+static void
+SMB2_sess_free_buffer(struct SMB2_sess_data *sess_data)
+{
+	free_rsp_buf(sess_data->buf0_type, sess_data->iov[0].iov_base);
+	sess_data->buf0_type = CIFS_NO_BUFFER;
+}
+
+static int
+SMB2_sess_sendreceive(struct SMB2_sess_data *sess_data)
+{
+	int rc;
+	struct smb2_sess_setup_req *req = sess_data->iov[0].iov_base;
+
+	/* Testing shows that buffer offset must be at location of Buffer[0] */
+	req->SecurityBufferOffset =
+		cpu_to_le16(sizeof(struct smb2_sess_setup_req) -
+			1 /* pad */ - 4 /* rfc1001 len */);
+	req->SecurityBufferLength = cpu_to_le16(sess_data->iov[1].iov_len);
+
+	inc_rfc1001_len(req, sess_data->iov[1].iov_len - 1 /* pad */);
+
+	/* BB add code to build os and lm fields */
+
+	rc = SendReceive2(sess_data->xid, sess_data->ses,
+				sess_data->iov, 2,
+				&sess_data->buf0_type,
+				CIFS_LOG_ERROR | CIFS_NEG_OP);
+
+	return rc;
+}
+
+static int
+SMB2_sess_establish_session(struct SMB2_sess_data *sess_data)
+{
+	int rc = 0;
+	struct cifs_ses *ses = sess_data->ses;
+
+	mutex_lock(&ses->server->srv_mutex);
+	if (ses->server->sign && ses->server->ops->generate_signingkey) {
+		rc = ses->server->ops->generate_signingkey(ses);
+		kfree(ses->auth_key.response);
+		ses->auth_key.response = NULL;
+		if (rc) {
+			cifs_dbg(FYI,
+				"SMB3 session key generation failed\n");
+			mutex_unlock(&ses->server->srv_mutex);
+			goto keygen_exit;
+		}
+	}
+	if (!ses->server->session_estab) {
+		ses->server->sequence_number = 0x2;
+		ses->server->session_estab = true;
+	}
+	mutex_unlock(&ses->server->srv_mutex);
+
+	cifs_dbg(FYI, "SMB2/3 session established successfully\n");
+	spin_lock(&GlobalMid_Lock);
+	ses->status = CifsGood;
+	ses->need_reconnect = false;
+	spin_unlock(&GlobalMid_Lock);
+
+keygen_exit:
+	if (!ses->server->sign) {
+		kfree(ses->auth_key.response);
+		ses->auth_key.response = NULL;
+	}
+	return rc;
+}
+
+#ifdef CONFIG_CIFS_UPCALL
+static void
+SMB2_auth_kerberos(struct SMB2_sess_data *sess_data)
+{
+	int rc;
+	struct cifs_ses *ses = sess_data->ses;
+	struct cifs_spnego_msg *msg;
+	struct key *spnego_key = NULL;
+	struct smb2_sess_setup_rsp *rsp = NULL;
+
+	rc = SMB2_sess_alloc_buffer(sess_data);
+	if (rc)
+		goto out;
+
+	spnego_key = cifs_get_spnego_key(ses);
+	if (IS_ERR(spnego_key)) {
+		rc = PTR_ERR(spnego_key);
+		spnego_key = NULL;
+		goto out;
+	}
+
+	msg = spnego_key->payload.data[0];
+	/*
+	 * 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) {
+		cifs_dbg(VFS,
+			  "bad cifs.upcall version. Expected %d got %d",
+			  CIFS_SPNEGO_UPCALL_VERSION, msg->version);
+		rc = -EKEYREJECTED;
+		goto out_put_spnego_key;
+	}
+
+	ses->auth_key.response = kmemdup(msg->data, msg->sesskey_len,
+					 GFP_KERNEL);
+	if (!ses->auth_key.response) {
+		cifs_dbg(VFS,
+			"Kerberos can't allocate (%u bytes) memory",
+			msg->sesskey_len);
+		rc = -ENOMEM;
+		goto out_put_spnego_key;
+	}
+	ses->auth_key.len = msg->sesskey_len;
+
+	sess_data->iov[1].iov_base = msg->data + msg->sesskey_len;
+	sess_data->iov[1].iov_len = msg->secblob_len;
+
+	rc = SMB2_sess_sendreceive(sess_data);
+	if (rc)
+		goto out_put_spnego_key;
+
+	rsp = (struct smb2_sess_setup_rsp *)sess_data->iov[0].iov_base;
+	ses->Suid = rsp->hdr.SessionId;
+
+	ses->session_flags = le16_to_cpu(rsp->SessionFlags);
+	if (ses->session_flags & SMB2_SESSION_FLAG_ENCRYPT_DATA)
+		cifs_dbg(VFS, "SMB3 encryption not supported yet\n");
+
+	rc = SMB2_sess_establish_session(sess_data);
+out_put_spnego_key:
+	key_invalidate(spnego_key);
+	key_put(spnego_key);
+out:
+	sess_data->result = rc;
+	sess_data->func = NULL;
+	SMB2_sess_free_buffer(sess_data);
+}
+#else
+static void
+SMB2_auth_kerberos(struct SMB2_sess_data *sess_data)
+{
+	cifs_dbg(VFS, "Kerberos negotiated but upcall support disabled!\n");
+	sess_data->result = -EOPNOTSUPP;
+	sess_data->func = NULL;
+}
+#endif
+
 int
 SMB2_sess_setup(const unsigned int xid, struct cifs_ses *ses,
 		const struct nls_table *nls_cp)
@@ -586,10 +791,10 @@  SMB2_sess_setup(const unsigned int xid, struct cifs_ses *ses,
 	__le32 phase = NtLmNegotiate; /* NTLMSSP, if needed, is multistage */
 	struct TCP_Server_Info *server = ses->server;
 	u16 blob_length = 0;
-	struct key *spnego_key = NULL;
 	char *security_blob = NULL;
 	unsigned char *ntlmssp_blob = NULL;
 	bool use_spnego = false; /* else use raw ntlmssp */
+	struct SMB2_sess_data *sess_data;
 
 	cifs_dbg(FYI, "Session Setup\n");
 
@@ -598,6 +803,19 @@  SMB2_sess_setup(const unsigned int xid, struct cifs_ses *ses,
 		return -EIO;
 	}
 
+	sess_data = kzalloc(sizeof(struct SMB2_sess_data), GFP_KERNEL);
+	if (!sess_data)
+		return -ENOMEM;
+	sess_data->xid = xid;
+	sess_data->ses = ses;
+	sess_data->buf0_type = CIFS_NO_BUFFER;
+	sess_data->nls_cp = (struct nls_table *) nls_cp;
+
+	if (ses->sectype == Kerberos) {
+		SMB2_auth_kerberos(sess_data);
+		goto out;
+	}
+
 	/*
 	 * If we are here due to reconnect, free per-smb session key
 	 * in case signing was required.
@@ -646,47 +864,7 @@  ssetup_ntlmssp_authenticate:
 	/* 4 for rfc1002 length field and 1 for pad */
 	iov[0].iov_len = get_rfc1002_length(req) + 4 - 1;
 
-	if (ses->sectype == Kerberos) {
-#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[0];
-		/*
-		 * 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) {
-			cifs_dbg(VFS,
-				  "bad cifs.upcall version. Expected %d got %d",
-				  CIFS_SPNEGO_UPCALL_VERSION, msg->version);
-			rc = -EKEYREJECTED;
-			goto ssetup_exit;
-		}
-		ses->auth_key.response = kmemdup(msg->data, msg->sesskey_len,
-						 GFP_KERNEL);
-		if (!ses->auth_key.response) {
-			cifs_dbg(VFS,
-				"Kerberos can't allocate (%u bytes) memory",
-				msg->sesskey_len);
-			rc = -ENOMEM;
-			goto ssetup_exit;
-		}
-		ses->auth_key.len = msg->sesskey_len;
-		blob_length = msg->secblob_len;
-		iov[1].iov_base = msg->data + msg->sesskey_len;
-		iov[1].iov_len = blob_length;
-#else
-		rc = -EOPNOTSUPP;
-		goto ssetup_exit;
-#endif /* CONFIG_CIFS_UPCALL */
-	} else if (phase == NtLmNegotiate) { /* if not krb5 must be ntlmssp */
+	if (phase == NtLmNegotiate) {
 		ntlmssp_blob = kmalloc(sizeof(struct _NEGOTIATE_MESSAGE),
 				       GFP_KERNEL);
 		if (ntlmssp_blob == NULL) {
@@ -829,13 +1007,13 @@  keygen_exit:
 		kfree(ses->auth_key.response);
 		ses->auth_key.response = NULL;
 	}
-	if (spnego_key) {
-		key_invalidate(spnego_key);
-		key_put(spnego_key);
-	}
 	kfree(ses->ntlmssp);
 
 	return rc;
+out:
+	rc = sess_data->result;
+	kfree(sess_data);
+	return rc;
 }
 
 int