diff options
author | Sachin Prabhu <sprabhu@redhat.com> | 2016-10-07 14:11:21 -0400 |
---|---|---|
committer | Steve French <smfrench@gmail.com> | 2016-10-13 20:48:30 -0400 |
commit | 3baf1a7b921500596b77487d5a34a27d656fc032 (patch) | |
tree | 01fa365ebac04681a957be2fe9c16bab1c411b00 | |
parent | cb978ac8b85fa8861352e57fcf8020f7f7bfbd82 (diff) |
SMB2: Separate Kerberos authentication from SMB2_sess_setup
Add helper functions and split Kerberos authentication off
SMB2_sess_setup.
Signed-off-by: Sachin Prabhu <sprabhu@redhat.com>
Signed-off-by: Steve French <smfrench@gmail.com>
Reviewed-by: Pavel Shilovsky <pshilov@microsoft.com>
-rw-r--r-- | fs/cifs/smb2pdu.c | 276 |
1 files changed, 230 insertions, 46 deletions
diff --git a/fs/cifs/smb2pdu.c b/fs/cifs/smb2pdu.c index d6a045690266..386b512189b2 100644 --- a/fs/cifs/smb2pdu.c +++ b/fs/cifs/smb2pdu.c | |||
@@ -593,6 +593,216 @@ vneg_out: | |||
593 | return -EIO; | 593 | return -EIO; |
594 | } | 594 | } |
595 | 595 | ||
596 | struct SMB2_sess_data { | ||
597 | unsigned int xid; | ||
598 | struct cifs_ses *ses; | ||
599 | struct nls_table *nls_cp; | ||
600 | void (*func)(struct SMB2_sess_data *); | ||
601 | int result; | ||
602 | u64 previous_session; | ||
603 | |||
604 | /* we will send the SMB in three pieces: | ||
605 | * a fixed length beginning part, an optional | ||
606 | * SPNEGO blob (which can be zero length), and a | ||
607 | * last part which will include the strings | ||
608 | * and rest of bcc area. This allows us to avoid | ||
609 | * a large buffer 17K allocation | ||
610 | */ | ||
611 | int buf0_type; | ||
612 | struct kvec iov[2]; | ||
613 | }; | ||
614 | |||
615 | static int | ||
616 | SMB2_sess_alloc_buffer(struct SMB2_sess_data *sess_data) | ||
617 | { | ||
618 | int rc; | ||
619 | struct cifs_ses *ses = sess_data->ses; | ||
620 | struct smb2_sess_setup_req *req; | ||
621 | struct TCP_Server_Info *server = ses->server; | ||
622 | |||
623 | rc = small_smb2_init(SMB2_SESSION_SETUP, NULL, (void **) &req); | ||
624 | if (rc) | ||
625 | return rc; | ||
626 | |||
627 | req->hdr.SessionId = 0; /* First session, not a reauthenticate */ | ||
628 | |||
629 | /* if reconnect, we need to send previous sess id, otherwise it is 0 */ | ||
630 | req->PreviousSessionId = sess_data->previous_session; | ||
631 | |||
632 | req->Flags = 0; /* MBZ */ | ||
633 | /* to enable echos and oplocks */ | ||
634 | req->hdr.CreditRequest = cpu_to_le16(3); | ||
635 | |||
636 | /* only one of SMB2 signing flags may be set in SMB2 request */ | ||
637 | if (server->sign) | ||
638 | req->SecurityMode = SMB2_NEGOTIATE_SIGNING_REQUIRED; | ||
639 | else if (global_secflags & CIFSSEC_MAY_SIGN) /* one flag unlike MUST_ */ | ||
640 | req->SecurityMode = SMB2_NEGOTIATE_SIGNING_ENABLED; | ||
641 | else | ||
642 | req->SecurityMode = 0; | ||
643 | |||
644 | req->Capabilities = 0; | ||
645 | req->Channel = 0; /* MBZ */ | ||
646 | |||
647 | sess_data->iov[0].iov_base = (char *)req; | ||
648 | /* 4 for rfc1002 length field and 1 for pad */ | ||
649 | sess_data->iov[0].iov_len = get_rfc1002_length(req) + 4 - 1; | ||
650 | /* | ||
651 | * This variable will be used to clear the buffer | ||
652 | * allocated above in case of any error in the calling function. | ||
653 | */ | ||
654 | sess_data->buf0_type = CIFS_SMALL_BUFFER; | ||
655 | |||
656 | return 0; | ||
657 | } | ||
658 | |||
659 | static void | ||
660 | SMB2_sess_free_buffer(struct SMB2_sess_data *sess_data) | ||
661 | { | ||
662 | free_rsp_buf(sess_data->buf0_type, sess_data->iov[0].iov_base); | ||
663 | sess_data->buf0_type = CIFS_NO_BUFFER; | ||
664 | } | ||
665 | |||
666 | static int | ||
667 | SMB2_sess_sendreceive(struct SMB2_sess_data *sess_data) | ||
668 | { | ||
669 | int rc; | ||
670 | struct smb2_sess_setup_req *req = sess_data->iov[0].iov_base; | ||
671 | |||
672 | /* Testing shows that buffer offset must be at location of Buffer[0] */ | ||
673 | req->SecurityBufferOffset = | ||
674 | cpu_to_le16(sizeof(struct smb2_sess_setup_req) - | ||
675 | 1 /* pad */ - 4 /* rfc1001 len */); | ||
676 | req->SecurityBufferLength = cpu_to_le16(sess_data->iov[1].iov_len); | ||
677 | |||
678 | inc_rfc1001_len(req, sess_data->iov[1].iov_len - 1 /* pad */); | ||
679 | |||
680 | /* BB add code to build os and lm fields */ | ||
681 | |||
682 | rc = SendReceive2(sess_data->xid, sess_data->ses, | ||
683 | sess_data->iov, 2, | ||
684 | &sess_data->buf0_type, | ||
685 | CIFS_LOG_ERROR | CIFS_NEG_OP); | ||
686 | |||
687 | return rc; | ||
688 | } | ||
689 | |||
690 | static int | ||
691 | SMB2_sess_establish_session(struct SMB2_sess_data *sess_data) | ||
692 | { | ||
693 | int rc = 0; | ||
694 | struct cifs_ses *ses = sess_data->ses; | ||
695 | |||
696 | mutex_lock(&ses->server->srv_mutex); | ||
697 | if (ses->server->sign && ses->server->ops->generate_signingkey) { | ||
698 | rc = ses->server->ops->generate_signingkey(ses); | ||
699 | kfree(ses->auth_key.response); | ||
700 | ses->auth_key.response = NULL; | ||
701 | if (rc) { | ||
702 | cifs_dbg(FYI, | ||
703 | "SMB3 session key generation failed\n"); | ||
704 | mutex_unlock(&ses->server->srv_mutex); | ||
705 | goto keygen_exit; | ||
706 | } | ||
707 | } | ||
708 | if (!ses->server->session_estab) { | ||
709 | ses->server->sequence_number = 0x2; | ||
710 | ses->server->session_estab = true; | ||
711 | } | ||
712 | mutex_unlock(&ses->server->srv_mutex); | ||
713 | |||
714 | cifs_dbg(FYI, "SMB2/3 session established successfully\n"); | ||
715 | spin_lock(&GlobalMid_Lock); | ||
716 | ses->status = CifsGood; | ||
717 | ses->need_reconnect = false; | ||
718 | spin_unlock(&GlobalMid_Lock); | ||
719 | |||
720 | keygen_exit: | ||
721 | if (!ses->server->sign) { | ||
722 | kfree(ses->auth_key.response); | ||
723 | ses->auth_key.response = NULL; | ||
724 | } | ||
725 | return rc; | ||
726 | } | ||
727 | |||
728 | #ifdef CONFIG_CIFS_UPCALL | ||
729 | static void | ||
730 | SMB2_auth_kerberos(struct SMB2_sess_data *sess_data) | ||
731 | { | ||
732 | int rc; | ||
733 | struct cifs_ses *ses = sess_data->ses; | ||
734 | struct cifs_spnego_msg *msg; | ||
735 | struct key *spnego_key = NULL; | ||
736 | struct smb2_sess_setup_rsp *rsp = NULL; | ||
737 | |||
738 | rc = SMB2_sess_alloc_buffer(sess_data); | ||
739 | if (rc) | ||
740 | goto out; | ||
741 | |||
742 | spnego_key = cifs_get_spnego_key(ses); | ||
743 | if (IS_ERR(spnego_key)) { | ||
744 | rc = PTR_ERR(spnego_key); | ||
745 | spnego_key = NULL; | ||
746 | goto out; | ||
747 | } | ||
748 | |||
749 | msg = spnego_key->payload.data[0]; | ||
750 | /* | ||
751 | * check version field to make sure that cifs.upcall is | ||
752 | * sending us a response in an expected form | ||
753 | */ | ||
754 | if (msg->version != CIFS_SPNEGO_UPCALL_VERSION) { | ||
755 | cifs_dbg(VFS, | ||
756 | "bad cifs.upcall version. Expected %d got %d", | ||
757 | CIFS_SPNEGO_UPCALL_VERSION, msg->version); | ||
758 | rc = -EKEYREJECTED; | ||
759 | goto out_put_spnego_key; | ||
760 | } | ||
761 | |||
762 | ses->auth_key.response = kmemdup(msg->data, msg->sesskey_len, | ||
763 | GFP_KERNEL); | ||
764 | if (!ses->auth_key.response) { | ||
765 | cifs_dbg(VFS, | ||
766 | "Kerberos can't allocate (%u bytes) memory", | ||
767 | msg->sesskey_len); | ||
768 | rc = -ENOMEM; | ||
769 | goto out_put_spnego_key; | ||
770 | } | ||
771 | ses->auth_key.len = msg->sesskey_len; | ||
772 | |||
773 | sess_data->iov[1].iov_base = msg->data + msg->sesskey_len; | ||
774 | sess_data->iov[1].iov_len = msg->secblob_len; | ||
775 | |||
776 | rc = SMB2_sess_sendreceive(sess_data); | ||
777 | if (rc) | ||
778 | goto out_put_spnego_key; | ||
779 | |||
780 | rsp = (struct smb2_sess_setup_rsp *)sess_data->iov[0].iov_base; | ||
781 | ses->Suid = rsp->hdr.SessionId; | ||
782 | |||
783 | ses->session_flags = le16_to_cpu(rsp->SessionFlags); | ||
784 | if (ses->session_flags & SMB2_SESSION_FLAG_ENCRYPT_DATA) | ||
785 | cifs_dbg(VFS, "SMB3 encryption not supported yet\n"); | ||
786 | |||
787 | rc = SMB2_sess_establish_session(sess_data); | ||
788 | out_put_spnego_key: | ||
789 | key_invalidate(spnego_key); | ||
790 | key_put(spnego_key); | ||
791 | out: | ||
792 | sess_data->result = rc; | ||
793 | sess_data->func = NULL; | ||
794 | SMB2_sess_free_buffer(sess_data); | ||
795 | } | ||
796 | #else | ||
797 | static void | ||
798 | SMB2_auth_kerberos(struct SMB2_sess_data *sess_data) | ||
799 | { | ||
800 | cifs_dbg(VFS, "Kerberos negotiated but upcall support disabled!\n"); | ||
801 | sess_data->result = -EOPNOTSUPP; | ||
802 | sess_data->func = NULL; | ||
803 | } | ||
804 | #endif | ||
805 | |||
596 | int | 806 | int |
597 | SMB2_sess_setup(const unsigned int xid, struct cifs_ses *ses, | 807 | SMB2_sess_setup(const unsigned int xid, struct cifs_ses *ses, |
598 | const struct nls_table *nls_cp) | 808 | const struct nls_table *nls_cp) |
@@ -605,11 +815,11 @@ SMB2_sess_setup(const unsigned int xid, struct cifs_ses *ses, | |||
605 | __le32 phase = NtLmNegotiate; /* NTLMSSP, if needed, is multistage */ | 815 | __le32 phase = NtLmNegotiate; /* NTLMSSP, if needed, is multistage */ |
606 | struct TCP_Server_Info *server = ses->server; | 816 | struct TCP_Server_Info *server = ses->server; |
607 | u16 blob_length = 0; | 817 | u16 blob_length = 0; |
608 | struct key *spnego_key = NULL; | ||
609 | char *security_blob = NULL; | 818 | char *security_blob = NULL; |
610 | unsigned char *ntlmssp_blob = NULL; | 819 | unsigned char *ntlmssp_blob = NULL; |
611 | bool use_spnego = false; /* else use raw ntlmssp */ | 820 | bool use_spnego = false; /* else use raw ntlmssp */ |
612 | u64 previous_session = ses->Suid; | 821 | u64 previous_session = ses->Suid; |
822 | struct SMB2_sess_data *sess_data; | ||
613 | 823 | ||
614 | cifs_dbg(FYI, "Session Setup\n"); | 824 | cifs_dbg(FYI, "Session Setup\n"); |
615 | 825 | ||
@@ -618,6 +828,20 @@ SMB2_sess_setup(const unsigned int xid, struct cifs_ses *ses, | |||
618 | return -EIO; | 828 | return -EIO; |
619 | } | 829 | } |
620 | 830 | ||
831 | sess_data = kzalloc(sizeof(struct SMB2_sess_data), GFP_KERNEL); | ||
832 | if (!sess_data) | ||
833 | return -ENOMEM; | ||
834 | sess_data->xid = xid; | ||
835 | sess_data->ses = ses; | ||
836 | sess_data->buf0_type = CIFS_NO_BUFFER; | ||
837 | sess_data->nls_cp = (struct nls_table *) nls_cp; | ||
838 | sess_data->previous_session = ses->Suid; | ||
839 | |||
840 | if (ses->sectype == Kerberos) { | ||
841 | SMB2_auth_kerberos(sess_data); | ||
842 | goto out; | ||
843 | } | ||
844 | |||
621 | /* | 845 | /* |
622 | * If we are here due to reconnect, free per-smb session key | 846 | * If we are here due to reconnect, free per-smb session key |
623 | * in case signing was required. | 847 | * in case signing was required. |
@@ -670,47 +894,7 @@ ssetup_ntlmssp_authenticate: | |||
670 | /* 4 for rfc1002 length field and 1 for pad */ | 894 | /* 4 for rfc1002 length field and 1 for pad */ |
671 | iov[0].iov_len = get_rfc1002_length(req) + 4 - 1; | 895 | iov[0].iov_len = get_rfc1002_length(req) + 4 - 1; |
672 | 896 | ||
673 | if (ses->sectype == Kerberos) { | 897 | if (phase == NtLmNegotiate) { |
674 | #ifdef CONFIG_CIFS_UPCALL | ||
675 | struct cifs_spnego_msg *msg; | ||
676 | |||
677 | spnego_key = cifs_get_spnego_key(ses); | ||
678 | if (IS_ERR(spnego_key)) { | ||
679 | rc = PTR_ERR(spnego_key); | ||
680 | spnego_key = NULL; | ||
681 | goto ssetup_exit; | ||
682 | } | ||
683 | |||
684 | msg = spnego_key->payload.data[0]; | ||
685 | /* | ||
686 | * check version field to make sure that cifs.upcall is | ||
687 | * sending us a response in an expected form | ||
688 | */ | ||
689 | if (msg->version != CIFS_SPNEGO_UPCALL_VERSION) { | ||
690 | cifs_dbg(VFS, | ||
691 | "bad cifs.upcall version. Expected %d got %d", | ||
692 | CIFS_SPNEGO_UPCALL_VERSION, msg->version); | ||
693 | rc = -EKEYREJECTED; | ||
694 | goto ssetup_exit; | ||
695 | } | ||
696 | ses->auth_key.response = kmemdup(msg->data, msg->sesskey_len, | ||
697 | GFP_KERNEL); | ||
698 | if (!ses->auth_key.response) { | ||
699 | cifs_dbg(VFS, | ||
700 | "Kerberos can't allocate (%u bytes) memory", | ||
701 | msg->sesskey_len); | ||
702 | rc = -ENOMEM; | ||
703 | goto ssetup_exit; | ||
704 | } | ||
705 | ses->auth_key.len = msg->sesskey_len; | ||
706 | blob_length = msg->secblob_len; | ||
707 | iov[1].iov_base = msg->data + msg->sesskey_len; | ||
708 | iov[1].iov_len = blob_length; | ||
709 | #else | ||
710 | rc = -EOPNOTSUPP; | ||
711 | goto ssetup_exit; | ||
712 | #endif /* CONFIG_CIFS_UPCALL */ | ||
713 | } else if (phase == NtLmNegotiate) { /* if not krb5 must be ntlmssp */ | ||
714 | ntlmssp_blob = kmalloc(sizeof(struct _NEGOTIATE_MESSAGE), | 898 | ntlmssp_blob = kmalloc(sizeof(struct _NEGOTIATE_MESSAGE), |
715 | GFP_KERNEL); | 899 | GFP_KERNEL); |
716 | if (ntlmssp_blob == NULL) { | 900 | if (ntlmssp_blob == NULL) { |
@@ -853,13 +1037,13 @@ keygen_exit: | |||
853 | kfree(ses->auth_key.response); | 1037 | kfree(ses->auth_key.response); |
854 | ses->auth_key.response = NULL; | 1038 | ses->auth_key.response = NULL; |
855 | } | 1039 | } |
856 | if (spnego_key) { | ||
857 | key_invalidate(spnego_key); | ||
858 | key_put(spnego_key); | ||
859 | } | ||
860 | kfree(ses->ntlmssp); | 1040 | kfree(ses->ntlmssp); |
861 | 1041 | ||
862 | return rc; | 1042 | return rc; |
1043 | out: | ||
1044 | rc = sess_data->result; | ||
1045 | kfree(sess_data); | ||
1046 | return rc; | ||
863 | } | 1047 | } |
864 | 1048 | ||
865 | int | 1049 | int |