Changeset View
Changeset View
Standalone View
Standalone View
sftp/kio_sftp.cpp
Show First 20 Lines • Show All 622 Lines • ▼ Show 20 Line(s) | 621 | if (rc < 0) { | |||
---|---|---|---|---|---|
623 | closeConnection(); | 623 | closeConnection(); | ||
624 | return false; | 624 | return false; | ||
625 | } | 625 | } | ||
626 | 626 | | |||
627 | return true; | 627 | return true; | ||
628 | } | 628 | } | ||
629 | 629 | | |||
630 | 630 | | |||
631 | void sftpProtocol::openConnection() { | 631 | #if LIBSSH_VERSION_INT >= SSH_VERSION_INT(0, 8, 3) | ||
632 | void sftpProtocol::openConnection() | ||||
633 | { | ||||
634 | if (mConnected) { | ||||
635 | return; | ||||
636 | } | ||||
637 | | ||||
638 | if (mHost.isEmpty()) { | ||||
639 | qCDebug(KIO_SFTP_LOG) << "openConnection(): Need hostname..."; | ||||
640 | error(KIO::ERR_UNKNOWN_HOST, QString()); | ||||
641 | return; | ||||
642 | } | ||||
643 | | ||||
644 | AuthInfo info; | ||||
645 | info.url.setScheme("sftp"); | ||||
646 | info.url.setHost(mHost); | ||||
647 | if ( mPort > 0 && mPort != DEFAULT_SFTP_PORT ) { | ||||
648 | info.url.setPort(mPort); | ||||
649 | } | ||||
650 | info.url.setUserName(mUsername); | ||||
651 | info.username = mUsername; | ||||
652 | | ||||
653 | // Check for cached authentication info if no password is specified... | ||||
654 | if (mPassword.isEmpty()) { | ||||
655 | qCDebug(KIO_SFTP_LOG) << "checking cache: info.username =" << info.username | ||||
656 | << ", info.url =" << info.url.toDisplayString(); | ||||
657 | checkCachedAuthentication(info); | ||||
658 | } else { | ||||
659 | info.password = mPassword; | ||||
660 | } | ||||
661 | | ||||
662 | // Start the ssh connection. | ||||
663 | QString msg; // msg for dialog box | ||||
664 | QString caption; // dialog box caption | ||||
665 | unsigned char *hash = nullptr; // the server hash | ||||
666 | size_t hlen; | ||||
667 | ssh_key srv_pubkey = nullptr; | ||||
668 | const char *srv_pubkey_type = nullptr; | ||||
669 | char *fingerprint = nullptr; | ||||
670 | enum ssh_known_hosts_e state; | ||||
671 | int rc; | ||||
672 | | ||||
673 | // Attempt to start a ssh session and establish a connection with the server. | ||||
674 | if (!sftpOpenConnection(info)) { | ||||
675 | return; | ||||
676 | } | ||||
677 | | ||||
678 | qCDebug(KIO_SFTP_LOG) << "Getting the SSH server hash"; | ||||
679 | | ||||
680 | /* get the hash */ | ||||
681 | rc = ssh_get_server_publickey(mSession, &srv_pubkey); | ||||
682 | if (rc < 0) { | ||||
683 | error(KIO::ERR_SLAVE_DEFINED, QString::fromUtf8(ssh_get_error(mSession))); | ||||
684 | closeConnection(); | ||||
685 | return; | ||||
686 | } | ||||
687 | | ||||
688 | srv_pubkey_type = ssh_key_type_to_char(ssh_key_type(srv_pubkey)); | ||||
689 | if (srv_pubkey_type == nullptr) { | ||||
690 | ssh_key_free(srv_pubkey); | ||||
691 | error(KIO::ERR_SLAVE_DEFINED, | ||||
692 | i18n("Could not get server public key type name")); | ||||
693 | closeConnection(); | ||||
694 | return; | ||||
695 | } | ||||
696 | | ||||
697 | rc = ssh_get_publickey_hash(srv_pubkey, | ||||
698 | SSH_PUBLICKEY_HASH_SHA256, | ||||
699 | &hash, | ||||
700 | &hlen); | ||||
701 | ssh_key_free(srv_pubkey); | ||||
702 | if (rc != SSH_OK) { | ||||
703 | error(KIO::ERR_SLAVE_DEFINED, | ||||
704 | i18n("Could not create hash from server public key")); | ||||
705 | closeConnection(); | ||||
706 | return; | ||||
707 | } | ||||
708 | | ||||
709 | fingerprint = ssh_get_fingerprint_hash(SSH_PUBLICKEY_HASH_SHA256, | ||||
710 | hash, | ||||
711 | hlen); | ||||
712 | ssh_string_free_char((char *)hash); | ||||
713 | if (fingerprint == nullptr) { | ||||
714 | error(KIO::ERR_SLAVE_DEFINED, | ||||
715 | i18n("Could not create fingerprint for server public key")); | ||||
716 | closeConnection(); | ||||
717 | return; | ||||
718 | } | ||||
719 | | ||||
720 | qCDebug(KIO_SFTP_LOG) << "Checking if the SSH server is known"; | ||||
721 | | ||||
722 | /* check the server public key hash */ | ||||
723 | state = ssh_session_is_known_server(mSession); | ||||
724 | switch (state) { | ||||
725 | case SSH_KNOWN_HOSTS_OTHER: | ||||
726 | ssh_string_free_char(fingerprint); | ||||
727 | error(KIO::ERR_SLAVE_DEFINED, | ||||
728 | i18n("An %1 host key for this server was " | ||||
729 | "not found, but another type of key exists.\n" | ||||
730 | "An attacker might change the default server key to confuse your " | ||||
731 | "client into thinking the key does not exist.\n" | ||||
732 | "Please contact your system administrator.\n" | ||||
733 | "%2", | ||||
734 | QString::fromUtf8(srv_pubkey_type), | ||||
735 | QString::fromUtf8(ssh_get_error(mSession)))); | ||||
736 | closeConnection(); | ||||
737 | return; | ||||
738 | case SSH_KNOWN_HOSTS_CHANGED: | ||||
739 | error(KIO::ERR_SLAVE_DEFINED, | ||||
740 | i18n("The host key for the server %1 has changed.\n" | ||||
741 | "This could either mean that DNS SPOOFING is happening or the IP " | ||||
742 | "address for the host and its host key have changed at the same time.\n" | ||||
743 | "The fingerprint for the %2 key sent by the remote host is:\n" | ||||
744 | " SHA256:%3\n" | ||||
745 | "Please contact your system administrator.\n%4", | ||||
746 | mHost, | ||||
747 | QString::fromUtf8(srv_pubkey_type), | ||||
748 | QString::fromUtf8(fingerprint), | ||||
749 | QString::fromUtf8(ssh_get_error(mSession)))); | ||||
750 | ssh_string_free_char(fingerprint); | ||||
751 | closeConnection(); | ||||
752 | return; | ||||
753 | case SSH_KNOWN_HOSTS_NOT_FOUND: | ||||
754 | case SSH_KNOWN_HOSTS_UNKNOWN: | ||||
755 | caption = i18n("Warning: Cannot verify host's identity."); | ||||
756 | msg = i18n("The authenticity of host %1 cannot be established.\n" | ||||
757 | "The %2 key fingerprint is: %3\n" | ||||
758 | "Are you sure you want to continue connecting?", | ||||
759 | mHost, | ||||
760 | QString::fromUtf8(srv_pubkey_type), | ||||
761 | QString::fromUtf8(fingerprint)); | ||||
762 | ssh_string_free_char(fingerprint); | ||||
763 | | ||||
764 | if (KMessageBox::Yes != messageBox(WarningYesNo, msg, caption)) { | ||||
765 | closeConnection(); | ||||
766 | error(KIO::ERR_USER_CANCELED, QString()); | ||||
767 | return; | ||||
768 | } | ||||
769 | | ||||
770 | /* write the known_hosts file */ | ||||
771 | qCDebug(KIO_SFTP_LOG) << "Adding server to known_hosts file."; | ||||
772 | rc = ssh_session_update_known_hosts(mSession); | ||||
773 | if (rc != SSH_OK) { | ||||
774 | error(KIO::ERR_USER_CANCELED, | ||||
775 | QString::fromUtf8(ssh_get_error(mSession))); | ||||
776 | closeConnection(); | ||||
777 | return; | ||||
778 | } | ||||
779 | break; | ||||
780 | case SSH_KNOWN_HOSTS_ERROR: | ||||
781 | ssh_string_free_char(fingerprint); | ||||
782 | error(KIO::ERR_SLAVE_DEFINED, | ||||
783 | QString::fromUtf8(ssh_get_error(mSession))); | ||||
784 | return; | ||||
785 | case SSH_KNOWN_HOSTS_OK: | ||||
786 | break; | ||||
787 | } | ||||
788 | | ||||
789 | qCDebug(KIO_SFTP_LOG) << "Trying to authenticate with the server"; | ||||
790 | | ||||
791 | // Try to login without authentication | ||||
792 | rc = ssh_userauth_none(mSession, nullptr); | ||||
793 | if (rc == SSH_AUTH_ERROR) { | ||||
794 | closeConnection(); | ||||
795 | error(KIO::ERR_COULD_NOT_LOGIN, i18n("Authentication failed.")); | ||||
796 | return; | ||||
797 | } | ||||
798 | | ||||
799 | // This NEEDS to be called after ssh_userauth_none() !!! | ||||
800 | int method = ssh_auth_list(mSession); | ||||
801 | if (rc != SSH_AUTH_SUCCESS && method == 0) { | ||||
802 | closeConnection(); | ||||
803 | error(KIO::ERR_COULD_NOT_LOGIN, i18n("Authentication failed. The server " | ||||
804 | "didn't send any authentication methods")); | ||||
805 | return; | ||||
806 | } | ||||
807 | | ||||
808 | // Try to authenticate with public key first | ||||
809 | if (rc != SSH_AUTH_SUCCESS && (method & SSH_AUTH_METHOD_PUBLICKEY)) { | ||||
810 | qCDebug(KIO_SFTP_LOG) << "Trying to authenticate with public key"; | ||||
811 | for(;;) { | ||||
812 | rc = ssh_userauth_publickey_auto(mSession, nullptr, nullptr); | ||||
813 | if (rc == SSH_AUTH_ERROR) { | ||||
814 | qCDebug(KIO_SFTP_LOG) << "Public key authentication failed:" << | ||||
815 | QString::fromUtf8(ssh_get_error(mSession)); | ||||
816 | closeConnection(); | ||||
817 | clearPubKeyAuthInfo(); | ||||
818 | error(KIO::ERR_COULD_NOT_LOGIN, i18n("Authentication failed.")); | ||||
819 | return; | ||||
820 | } else if (rc != SSH_AUTH_DENIED || !mPublicKeyAuthInfo || !mPublicKeyAuthInfo->isModified()) { | ||||
821 | clearPubKeyAuthInfo(); | ||||
822 | break; | ||||
823 | } | ||||
824 | } | ||||
825 | } | ||||
826 | | ||||
827 | // Try to authenticate with GSSAPI | ||||
828 | if (rc != SSH_AUTH_SUCCESS && (method & SSH_AUTH_METHOD_GSSAPI_MIC)) { | ||||
829 | qCDebug(KIO_SFTP_LOG) << "Trying to authenticate with GSSAPI"; | ||||
830 | rc = ssh_userauth_gssapi(mSession); | ||||
831 | if (rc == SSH_AUTH_ERROR) { | ||||
832 | qCDebug(KIO_SFTP_LOG) << "Public key authentication failed:" << | ||||
833 | QString::fromUtf8(ssh_get_error(mSession)); | ||||
834 | closeConnection(); | ||||
835 | error(KIO::ERR_COULD_NOT_LOGIN, i18n("Authentication failed.")); | ||||
836 | return; | ||||
837 | } | ||||
838 | } | ||||
632 | 839 | | |||
840 | // Try to authenticate with keyboard interactive | ||||
841 | if (rc != SSH_AUTH_SUCCESS && (method & SSH_AUTH_METHOD_INTERACTIVE)) { | ||||
842 | qCDebug(KIO_SFTP_LOG) << "Trying to authenticate with keyboard interactive"; | ||||
843 | AuthInfo info2 (info); | ||||
844 | rc = authenticateKeyboardInteractive(info2); | ||||
845 | if (rc == SSH_AUTH_SUCCESS) { | ||||
846 | info = info2; | ||||
847 | } else if (rc == SSH_AUTH_ERROR) { | ||||
848 | qCDebug(KIO_SFTP_LOG) << "Keyboard interactive authentication failed:" | ||||
849 | << QString::fromUtf8(ssh_get_error(mSession)); | ||||
850 | closeConnection(); | ||||
851 | error(KIO::ERR_COULD_NOT_LOGIN, i18n("Authentication failed.")); | ||||
852 | return; | ||||
853 | } | ||||
854 | } | ||||
855 | | ||||
856 | // Try to authenticate with password | ||||
857 | if (rc != SSH_AUTH_SUCCESS && (method & SSH_AUTH_METHOD_PASSWORD)) { | ||||
858 | qCDebug(KIO_SFTP_LOG) << "Trying to authenticate with password"; | ||||
859 | | ||||
860 | info.caption = i18n("SFTP Login"); | ||||
861 | info.prompt = i18n("Please enter your username and password."); | ||||
862 | info.comment = info.url.url(); | ||||
863 | info.commentLabel = i18n("Site:"); | ||||
864 | bool isFirstLoginAttempt = true; | ||||
865 | | ||||
866 | for(;;) { | ||||
867 | if (!isFirstLoginAttempt || info.password.isEmpty()) { | ||||
868 | info.keepPassword = true; // make the "keep Password" check box visible to the user. | ||||
869 | info.setModified(false); | ||||
870 | | ||||
871 | QString username (info.username); | ||||
872 | const QString errMsg(isFirstLoginAttempt ? QString() : i18n("Incorrect username or password")); | ||||
873 | | ||||
874 | qCDebug(KIO_SFTP_LOG) << "Username:" << username << "first attempt?" | ||||
875 | << isFirstLoginAttempt << "error:" << errMsg; | ||||
876 | | ||||
877 | // Handle user canceled or dialog failed to open... | ||||
878 | | ||||
879 | int errCode = openPasswordDialogV2(info, errMsg); | ||||
880 | if (errCode != 0) { | ||||
881 | qCDebug(KIO_SFTP_LOG) << "User canceled password/retry dialog"; | ||||
882 | closeConnection(); | ||||
883 | error(errCode, QString()); | ||||
884 | return; | ||||
885 | } | ||||
886 | | ||||
887 | // If the user name changes, we have to restablish connection again | ||||
888 | // since the user name must always be set before calling ssh_connect. | ||||
889 | if (wasUsernameChanged(username, info)) { | ||||
890 | qCDebug(KIO_SFTP_LOG) << "Username changed to" << info.username; | ||||
891 | if (!info.url.userName().isEmpty()) { | ||||
892 | info.url.setUserName(info.username); | ||||
893 | } | ||||
894 | closeConnection(); | ||||
895 | if (!sftpOpenConnection(info)) { | ||||
896 | return; | ||||
897 | } | ||||
898 | } | ||||
899 | } | ||||
900 | | ||||
901 | rc = ssh_userauth_password(mSession, info.username.toUtf8().constData(), info.password.toUtf8().constData()); | ||||
902 | if (rc == SSH_AUTH_SUCCESS) { | ||||
903 | break; | ||||
904 | } else if (rc == SSH_AUTH_ERROR) { | ||||
905 | qCDebug(KIO_SFTP_LOG) << "Password authentication failed:" | ||||
906 | << QString::fromUtf8(ssh_get_error(mSession)); | ||||
907 | closeConnection(); | ||||
908 | error(KIO::ERR_COULD_NOT_LOGIN, i18n("Authentication failed.")); | ||||
909 | return; | ||||
910 | } | ||||
911 | | ||||
912 | isFirstLoginAttempt = false; // failed attempt to login. | ||||
913 | info.password.clear(); // clear the password after failed attempts. | ||||
914 | } | ||||
915 | } | ||||
916 | | ||||
917 | // If we're still not authenticated then we need to leave. | ||||
918 | if (rc != SSH_AUTH_SUCCESS) { | ||||
919 | error(KIO::ERR_COULD_NOT_LOGIN, i18n("Authentication failed.")); | ||||
920 | return; | ||||
921 | } | ||||
922 | | ||||
923 | // start sftp session | ||||
924 | qCDebug(KIO_SFTP_LOG) << "Trying to request the sftp session"; | ||||
925 | mSftp = sftp_new(mSession); | ||||
926 | if (mSftp == nullptr) { | ||||
927 | closeConnection(); | ||||
928 | error(KIO::ERR_COULD_NOT_LOGIN, i18n("Unable to request the SFTP subsystem. " | ||||
929 | "Make sure SFTP is enabled on the server.")); | ||||
930 | return; | ||||
931 | } | ||||
932 | | ||||
933 | qCDebug(KIO_SFTP_LOG) << "Trying to initialize the sftp session"; | ||||
934 | if (sftp_init(mSftp) < 0) { | ||||
935 | closeConnection(); | ||||
936 | error(KIO::ERR_COULD_NOT_LOGIN, i18n("Could not initialize the SFTP session.")); | ||||
937 | return; | ||||
938 | } | ||||
939 | | ||||
940 | // Login succeeded! | ||||
941 | infoMessage(i18n("Successfully connected to %1", mHost)); | ||||
942 | if (info.keepPassword) { | ||||
943 | qCDebug(KIO_SFTP_LOG) << "Caching info.username = " << info.username | ||||
944 | << ", info.url = " << info.url.toDisplayString(); | ||||
945 | cacheAuthentication(info); | ||||
946 | } | ||||
947 | | ||||
948 | // Update the original username in case it was changed! | ||||
949 | if (!mUsername.isEmpty()) { | ||||
950 | mUsername = info.username; | ||||
951 | } | ||||
952 | | ||||
953 | setTimeoutSpecialCommand(KIO_SFTP_SPECIAL_TIMEOUT); | ||||
954 | | ||||
955 | mConnected = true; | ||||
956 | connected(); | ||||
957 | | ||||
958 | info.password.fill('x'); | ||||
959 | info.password.clear(); | ||||
960 | } | ||||
961 | #else // < 0.8.0 | ||||
962 | void sftpProtocol::openConnection() | ||||
963 | { | ||||
633 | if (mConnected) { | 964 | if (mConnected) { | ||
634 | return; | 965 | return; | ||
635 | } | 966 | } | ||
636 | 967 | | |||
637 | if (mHost.isEmpty()) { | 968 | if (mHost.isEmpty()) { | ||
638 | qCDebug(KIO_SFTP_LOG) << "openConnection(): Need hostname..."; | 969 | qCDebug(KIO_SFTP_LOG) << "openConnection(): Need hostname..."; | ||
639 | error(KIO::ERR_UNKNOWN_HOST, QString()); | 970 | error(KIO::ERR_UNKNOWN_HOST, QString()); | ||
640 | return; | 971 | return; | ||
▲ Show 20 Lines • Show All 279 Lines • ▼ Show 20 Line(s) | |||||
920 | setTimeoutSpecialCommand(KIO_SFTP_SPECIAL_TIMEOUT); | 1251 | setTimeoutSpecialCommand(KIO_SFTP_SPECIAL_TIMEOUT); | ||
921 | 1252 | | |||
922 | mConnected = true; | 1253 | mConnected = true; | ||
923 | connected(); | 1254 | connected(); | ||
924 | 1255 | | |||
925 | info.password.fill('x'); | 1256 | info.password.fill('x'); | ||
926 | info.password.clear(); | 1257 | info.password.clear(); | ||
927 | } | 1258 | } | ||
1259 | #endif // 0.8.0 | ||||
928 | 1260 | | |||
929 | void sftpProtocol::closeConnection() { | 1261 | void sftpProtocol::closeConnection() { | ||
930 | qCDebug(KIO_SFTP_LOG); | 1262 | qCDebug(KIO_SFTP_LOG); | ||
931 | 1263 | | |||
932 | if (mSftp) { | 1264 | if (mSftp) { | ||
933 | sftp_free(mSftp); | 1265 | sftp_free(mSftp); | ||
934 | mSftp = nullptr; | 1266 | mSftp = nullptr; | ||
935 | } | 1267 | } | ||
▲ Show 20 Lines • Show All 1372 Lines • Show Last 20 Lines |