|
|
@ -984,8 +984,6 @@ int sftpProtocol::initializeConnection() {
|
|
|
|
return rc;
|
|
|
|
return rc;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
ExitGuard connectionCloser([this](){ closeConnection(); });
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
kdDebug(TDEIO_SFTP_DB) << "Getting the SSH server hash" << endl;
|
|
|
|
kdDebug(TDEIO_SFTP_DB) << "Getting the SSH server hash" << endl;
|
|
|
|
|
|
|
|
|
|
|
|
/* get the hash */
|
|
|
|
/* get the hash */
|
|
|
@ -1079,8 +1077,6 @@ int sftpProtocol::initializeConnection() {
|
|
|
|
|
|
|
|
|
|
|
|
kdDebug(TDEIO_SFTP_DB) << "Trying to authenticate with the server" << endl;
|
|
|
|
kdDebug(TDEIO_SFTP_DB) << "Trying to authenticate with the server" << endl;
|
|
|
|
|
|
|
|
|
|
|
|
connectionCloser.abort();
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
return SSH_OK;
|
|
|
|
return SSH_OK;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
@ -1121,135 +1117,140 @@ void sftpProtocol::openConnection() {
|
|
|
|
PasswordPurger pwPurger{mPassword};
|
|
|
|
PasswordPurger pwPurger{mPassword};
|
|
|
|
|
|
|
|
|
|
|
|
int rc;
|
|
|
|
int rc;
|
|
|
|
|
|
|
|
|
|
|
|
connection_restart:
|
|
|
|
|
|
|
|
// Start the ssh connection.
|
|
|
|
|
|
|
|
if (initializeConnection() < 0) {
|
|
|
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
ExitGuard connectionCloser([this](){ closeConnection(); });
|
|
|
|
ExitGuard connectionCloser([this](){ closeConnection(); });
|
|
|
|
|
|
|
|
|
|
|
|
// Try to authenticate (this required before calling ssh_auth_list())
|
|
|
|
do { // A loop to restart connection when needed
|
|
|
|
rc = ssh_userauth_none(mSession, NULL);
|
|
|
|
// Start the ssh connection.
|
|
|
|
if (rc == SSH_AUTH_ERROR) {
|
|
|
|
if (initializeConnection() < 0) {
|
|
|
|
error(TDEIO::ERR_COULD_NOT_LOGIN, sshError(i18n("Authentication failed (method: %1).")
|
|
|
|
return;
|
|
|
|
.arg(i18n("none"))));
|
|
|
|
}
|
|
|
|
return;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// Preinit the list of supported auth methods
|
|
|
|
|
|
|
|
static const auto authMethodsNormal = [](){
|
|
|
|
|
|
|
|
std::vector<std::unique_ptr<SSHAuthMethod>> rv;
|
|
|
|
|
|
|
|
rv.emplace_back(std::make_unique<PublicKeyAuth>());
|
|
|
|
|
|
|
|
rv.emplace_back(std::make_unique<KeyboardInteractiveAuth>());
|
|
|
|
|
|
|
|
rv.emplace_back(std::make_unique<PasswordAuth>());
|
|
|
|
|
|
|
|
return rv;
|
|
|
|
|
|
|
|
}();
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const static int supportedMethods = std::accumulate(
|
|
|
|
// Try to authenticate (this required before calling ssh_auth_list())
|
|
|
|
authMethodsNormal.begin(), authMethodsNormal.end(),
|
|
|
|
rc = ssh_userauth_none(mSession, NULL);
|
|
|
|
SSH_AUTH_METHOD_NONE, //< none is supported by default
|
|
|
|
if (rc == SSH_AUTH_ERROR) {
|
|
|
|
[](int acc, const auto &m){ return acc |= m->flag(); });
|
|
|
|
error(TDEIO::ERR_COULD_NOT_LOGIN, sshError(i18n("Authentication failed (method: %1).")
|
|
|
|
|
|
|
|
.arg(i18n("none"))));
|
|
|
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
unsigned attemptedMethods = 0;
|
|
|
|
// Preinit the list of supported auth methods
|
|
|
|
|
|
|
|
static const auto authMethodsNormal = [](){
|
|
|
|
|
|
|
|
std::vector<std::unique_ptr<SSHAuthMethod>> rv;
|
|
|
|
|
|
|
|
rv.emplace_back(std::make_unique<PublicKeyAuth>());
|
|
|
|
|
|
|
|
rv.emplace_back(std::make_unique<KeyboardInteractiveAuth>());
|
|
|
|
|
|
|
|
rv.emplace_back(std::make_unique<PasswordAuth>());
|
|
|
|
|
|
|
|
return rv;
|
|
|
|
|
|
|
|
}();
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const static int supportedMethods = std::accumulate(
|
|
|
|
|
|
|
|
authMethodsNormal.begin(), authMethodsNormal.end(),
|
|
|
|
|
|
|
|
SSH_AUTH_METHOD_NONE, //< none is supported by default
|
|
|
|
|
|
|
|
[](int acc, const auto &m){ return acc |= m->flag(); });
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
unsigned attemptedMethods = 0;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// Backup of the value of the SlaveBase::s_seqNr. This is used to query different data values
|
|
|
|
|
|
|
|
// with openPassDlg() with the same seqNr. Otherwise it will result in the prompting of the pass
|
|
|
|
|
|
|
|
// dialog to the user in cases the values should be recovered from the cache.
|
|
|
|
|
|
|
|
// This is a bit hacky but necessary
|
|
|
|
|
|
|
|
long current_seqNr = SlaveBase::s_seqNr;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
while (rc != SSH_AUTH_SUCCESS) {
|
|
|
|
|
|
|
|
// Note this loop can rerun in case of multistage ssh authentication e.g. "password,publickey"
|
|
|
|
|
|
|
|
// which will require user to provide a valid password at first and then a valid public key.
|
|
|
|
|
|
|
|
// see AuthenticationMethods in man 5 sshd_config for more info
|
|
|
|
|
|
|
|
bool wasCanceled = false;
|
|
|
|
|
|
|
|
unsigned availableMethodes = ssh_auth_list(mSession);
|
|
|
|
|
|
|
|
|
|
|
|
// Backup of the value of the SlaveBase::s_seqNr. This is used to query different data values
|
|
|
|
SlaveBase::s_seqNr = current_seqNr;
|
|
|
|
// with openPassDlg() with the same seqNr. Otherwise it will result in the prompting of the pass
|
|
|
|
|
|
|
|
// dialog to the user in cases the values should be recovered from the cache.
|
|
|
|
|
|
|
|
// This is a bit hacky but necessary
|
|
|
|
|
|
|
|
long current_seqNr = SlaveBase::s_seqNr;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
while (rc != SSH_AUTH_SUCCESS) {
|
|
|
|
if (!availableMethodes) {
|
|
|
|
// Note this loop can rerun in case of multistage ssh authentication e.g. "password,publickey"
|
|
|
|
// Technically libssh docs suggest that the server merely MAY send auth methods, but it's
|
|
|
|
// which will require user to provide a valid password at first and then a valid public key.
|
|
|
|
// highly unclear what we should do in such case and it looks like openssh doesn't have an
|
|
|
|
// see AuthenticationMethods in man 5 sshd_config for more info
|
|
|
|
// option for that, so let's just consider this server a jerk and don't talk to him anymore.
|
|
|
|
bool wasCanceled = false;
|
|
|
|
error(TDEIO::ERR_COULD_NOT_LOGIN, i18n("Authentication failed.\n"
|
|
|
|
unsigned availableMethodes = ssh_auth_list(mSession);
|
|
|
|
"The server did not send any authentication methods!"));
|
|
|
|
|
|
|
|
return;
|
|
|
|
SlaveBase::s_seqNr = current_seqNr;
|
|
|
|
} else if (!(availableMethodes & supportedMethods)) {
|
|
|
|
|
|
|
|
error(TDEIO::ERR_COULD_NOT_LOGIN, i18n("Authentication failed.\n"
|
|
|
|
if (!availableMethodes) {
|
|
|
|
"The server sent only unsupported authentication methods (%1)!")
|
|
|
|
// Technically libssh docs suggest that the server merely MAY send auth methods, but it's
|
|
|
|
.arg(SSHAuthMethod::bitsetToStr(availableMethodes).join(", ")));
|
|
|
|
// highly unclear what we should do in such case and it looks like openssh doesn't have an
|
|
|
|
return;
|
|
|
|
// option for that, so let's just consider this server a jerk and don't talk to him anymore.
|
|
|
|
}
|
|
|
|
error(TDEIO::ERR_COULD_NOT_LOGIN, i18n("Authentication failed.\n"
|
|
|
|
|
|
|
|
"The server did not send any authentication methods!"));
|
|
|
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
} else if (!(availableMethodes & supportedMethods)) {
|
|
|
|
|
|
|
|
error(TDEIO::ERR_COULD_NOT_LOGIN, i18n("Authentication failed.\n"
|
|
|
|
|
|
|
|
"The server sent only unsupported authentication methods (%1)!")
|
|
|
|
|
|
|
|
.arg(SSHAuthMethod::bitsetToStr(availableMethodes).join(", ")));
|
|
|
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const auto *authMethods = &authMethodsNormal;
|
|
|
|
const auto *authMethods = &authMethodsNormal;
|
|
|
|
|
|
|
|
|
|
|
|
// If we have cached password we want try to use it before public key
|
|
|
|
// If we have cached password we want try to use it before public key
|
|
|
|
if(!mPassword.isEmpty()) {
|
|
|
|
if(!mPassword.isEmpty()) {
|
|
|
|
static const auto authMethodsWithPassword = []() {
|
|
|
|
static const auto authMethodsWithPassword = []() {
|
|
|
|
std::vector<std::unique_ptr<SSHAuthMethod>> rv;
|
|
|
|
std::vector<std::unique_ptr<SSHAuthMethod>> rv;
|
|
|
|
rv.emplace_back(std::make_unique<KeyboardInteractiveAuth>(/* noPasswordQuery = */true));
|
|
|
|
rv.emplace_back(std::make_unique<KeyboardInteractiveAuth>(/* noPasswordQuery = */true));
|
|
|
|
rv.emplace_back(std::make_unique<PasswordAuth>(/* noPasswordQuery = */true));
|
|
|
|
rv.emplace_back(std::make_unique<PasswordAuth>(/* noPasswordQuery = */true));
|
|
|
|
for (const auto &m: authMethodsNormal) { rv.emplace_back(m->clone()); }
|
|
|
|
for (const auto &m: authMethodsNormal) { rv.emplace_back(m->clone()); }
|
|
|
|
return rv;
|
|
|
|
return rv;
|
|
|
|
}();
|
|
|
|
}();
|
|
|
|
|
|
|
|
|
|
|
|
authMethods = &authMethodsWithPassword;
|
|
|
|
authMethods = &authMethodsWithPassword;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Actually iterate over the list of methods and try them out
|
|
|
|
// Actually iterate over the list of methods and try them out
|
|
|
|
for (const auto &method: *authMethods) {
|
|
|
|
for (const auto &method: *authMethods) {
|
|
|
|
if (!(availableMethodes & method->flag())) { continue; }
|
|
|
|
if (!(availableMethodes & method->flag())) { continue; }
|
|
|
|
|
|
|
|
|
|
|
|
rc = method->authenticate( this );
|
|
|
|
rc = method->authenticate( this );
|
|
|
|
attemptedMethods |= method->flag();
|
|
|
|
attemptedMethods |= method->flag();
|
|
|
|
if (rc == SSH_AUTH_SUCCESS || rc == SSH_AUTH_PARTIAL) {
|
|
|
|
if (rc == SSH_AUTH_SUCCESS || rc == SSH_AUTH_PARTIAL) {
|
|
|
|
kdDebug(TDEIO_SFTP_DB) << "method=" << method->name() << ": auth "
|
|
|
|
kdDebug(TDEIO_SFTP_DB) << "method=" << method->name() << ": auth "
|
|
|
|
<< (rc == SSH_AUTH_SUCCESS ? "success" : "partial") << endl;
|
|
|
|
<< (rc == SSH_AUTH_SUCCESS ? "success" : "partial") << endl;
|
|
|
|
break; // either next auth method or continue on with the connect
|
|
|
|
break; // either next auth method or continue on with the connect
|
|
|
|
} else if (rc == SSH_AUTH_ERROR || rc == SSH_AUTH_AGAIN) {
|
|
|
|
} else if (rc == SSH_AUTH_ERROR || rc == SSH_AUTH_AGAIN) {
|
|
|
|
TQString errMsg = i18n("Authentication failed (method: %1).").arg(method->name());
|
|
|
|
TQString errMsg = i18n("Authentication failed (method: %1).").arg(method->name());
|
|
|
|
// SSH_AUTH_AGAIN returned in case of some errors when server hangs up unexpectedly like
|
|
|
|
// SSH_AUTH_AGAIN returned in case of some errors when server hangs up unexpectedly like
|
|
|
|
// in case there were too many failed authentication attempts
|
|
|
|
// in case there were too many failed authentication attempts
|
|
|
|
if (rc == SSH_AUTH_AGAIN) {
|
|
|
|
if (rc == SSH_AUTH_AGAIN) {
|
|
|
|
errMsg.append("\n").append(i18n("Server is slow to respond or hung up unexpectedly."));
|
|
|
|
errMsg.append("\n").append(i18n("Server is slow to respond or hung up unexpectedly."));
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
error(TDEIO::ERR_COULD_NOT_LOGIN, sshError(errMsg));
|
|
|
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
} else if (rc == SSH_AUTH_CANCELED) {
|
|
|
|
|
|
|
|
kdDebug(TDEIO_SFTP_DB) << "method=" << method->name() << " was canceled by user" << endl;
|
|
|
|
|
|
|
|
// don't quit immediately due to that the user might have canceled one method to use another
|
|
|
|
|
|
|
|
wasCanceled = true;
|
|
|
|
|
|
|
|
} else if (rc == SSH_AUTH_NEED_RECONNECT) {
|
|
|
|
|
|
|
|
kdDebug(TDEIO_SFTP_DB) << "method=" << method->name() << " requested reconnection" << endl;
|
|
|
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
} else if (rc == SSH_AUTH_DENIED) {
|
|
|
|
|
|
|
|
kdDebug(TDEIO_SFTP_DB) << "Auth for method=" << method->name() << " was denied" << endl;
|
|
|
|
|
|
|
|
// do nothing, just proceed with next auth method
|
|
|
|
|
|
|
|
} else {
|
|
|
|
|
|
|
|
// Shouldn't happen, but to be on the safe side better handle it
|
|
|
|
|
|
|
|
error(TDEIO::ERR_UNKNOWN, sshError(i18n("Authentication failed unexpectedly")));
|
|
|
|
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
error(TDEIO::ERR_COULD_NOT_LOGIN, sshError(errMsg));
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// At this point rc values should be one of:
|
|
|
|
|
|
|
|
// SSH_AUTH_SUCCESS, SSH_AUTH_PARTIAL, SSH_AUTH_DENIED, SSH_AUTH_CANCELED or SSH_AUTH_NEED_RECONNECT
|
|
|
|
|
|
|
|
if(rc == SSH_AUTH_NEED_RECONNECT) {
|
|
|
|
|
|
|
|
closeConnection(); //< have to do it manually
|
|
|
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
} else if (wasCanceled && (rc == SSH_AUTH_CANCELED || rc == SSH_AUTH_DENIED)) {
|
|
|
|
|
|
|
|
error(TDEIO::ERR_USER_CANCELED, TQString::null);
|
|
|
|
return;
|
|
|
|
return;
|
|
|
|
} else if (rc == SSH_AUTH_CANCELED) {
|
|
|
|
} else if (rc != SSH_AUTH_SUCCESS && rc != SSH_AUTH_PARTIAL) {
|
|
|
|
kdDebug(TDEIO_SFTP_DB) << "method=" << method->name() << " was canceled by user" << endl;
|
|
|
|
TQString errMsg = i18n("Authentication denied (attempted methods: %1).")
|
|
|
|
// don't quit immediately due to that the user might have canceled one method to use another
|
|
|
|
.arg(SSHAuthMethod::bitsetToStr(attemptedMethods).join(", "));
|
|
|
|
wasCanceled = true;
|
|
|
|
if (availableMethodes & ~supportedMethods) {
|
|
|
|
} else if (rc == SSH_AUTH_NEED_RECONNECT) {
|
|
|
|
errMsg.append("\n")
|
|
|
|
kdDebug(TDEIO_SFTP_DB) << "method=" << method->name() << " requested reconnection" << endl;
|
|
|
|
.append(i18n("Note: server also declares some unsupported authentication methods (%1)")
|
|
|
|
goto connection_restart;
|
|
|
|
.arg(SSHAuthMethod::bitsetToStr(availableMethodes & ~supportedMethods).join(", ")));
|
|
|
|
} else if (rc == SSH_AUTH_DENIED) {
|
|
|
|
}
|
|
|
|
kdDebug(TDEIO_SFTP_DB) << "Auth for method=" << method->name() << " was denied" << endl;
|
|
|
|
error(TDEIO::ERR_COULD_NOT_LOGIN, errMsg);
|
|
|
|
// do nothing, just proceed with next auth method
|
|
|
|
|
|
|
|
} else {
|
|
|
|
|
|
|
|
// Shouldn't happen, but to be on the safe side better handle it
|
|
|
|
|
|
|
|
error(TDEIO::ERR_UNKNOWN, sshError(i18n("Authentication failed unexpectedly")));
|
|
|
|
|
|
|
|
return;
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} // while (rc != SSH_AUTH_SUCCESS)
|
|
|
|
|
|
|
|
} while(rc == SSH_AUTH_NEED_RECONNECT);
|
|
|
|
|
|
|
|
|
|
|
|
// At this point rc values should be one of:
|
|
|
|
|
|
|
|
// SSH_AUTH_SUCCESS, SSH_AUTH_PARTIAL, SSH_AUTH_DENIED or SSH_AUTH_CANCELED
|
|
|
|
|
|
|
|
if (wasCanceled && (rc == SSH_AUTH_CANCELED || rc == SSH_AUTH_DENIED)) {
|
|
|
|
|
|
|
|
error(TDEIO::ERR_USER_CANCELED, TQString::null);
|
|
|
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
} else if (rc != SSH_AUTH_SUCCESS && rc != SSH_AUTH_PARTIAL) {
|
|
|
|
|
|
|
|
TQString errMsg = i18n("Authentication denied (attempted methods: %1).")
|
|
|
|
|
|
|
|
.arg(SSHAuthMethod::bitsetToStr(attemptedMethods).join(", "));
|
|
|
|
|
|
|
|
if (availableMethodes & ~supportedMethods) {
|
|
|
|
|
|
|
|
errMsg.append("\n")
|
|
|
|
|
|
|
|
.append(i18n("Note: server also declares some unsupported authentication methods (%1)")
|
|
|
|
|
|
|
|
.arg(SSHAuthMethod::bitsetToStr(availableMethodes & ~supportedMethods).join(", ")));
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
error(TDEIO::ERR_COULD_NOT_LOGIN, errMsg);
|
|
|
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
} // while (rc != SSH_AUTH_SUCCESS)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// start sftp session
|
|
|
|
// start sftp session
|
|
|
|
kdDebug(TDEIO_SFTP_DB) << "Trying to request the sftp session" << endl;
|
|
|
|
kdDebug(TDEIO_SFTP_DB) << "Trying to request the sftp session" << endl;
|
|
|
|