summaryrefslogtreecommitdiffstats
path: root/tdeioslave/sftp/tdeio_sftp.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'tdeioslave/sftp/tdeio_sftp.cpp')
-rw-r--r--tdeioslave/sftp/tdeio_sftp.cpp941
1 files changed, 663 insertions, 278 deletions
diff --git a/tdeioslave/sftp/tdeio_sftp.cpp b/tdeioslave/sftp/tdeio_sftp.cpp
index d62e7e062..3e88b8e65 100644
--- a/tdeioslave/sftp/tdeio_sftp.cpp
+++ b/tdeioslave/sftp/tdeio_sftp.cpp
@@ -33,6 +33,10 @@
#include <tqfile.h>
#include <tqdir.h>
+#include <numeric>
+#include <functional>
+#include <vector>
+
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
@@ -50,7 +54,7 @@
#include <kdebug.h>
#include <tdemessagebox.h>
#include <tdeglobal.h>
-#include <kstandarddirs.h>
+#include <tdestandarddirs.h>
#include <tdelocale.h>
#include <kurl.h>
#include <tdeio/ioslave_defaults.h>
@@ -68,7 +72,7 @@
using namespace TDEIO;
extern "C"
{
- int KDE_EXPORT kdemain( int argc, char **argv )
+ int TDE_EXPORT kdemain( int argc, char **argv )
{
TDEInstance instance( "tdeio_sftp" );
@@ -92,6 +96,54 @@ extern "C"
}
}
+// Some helper functions/classes
+namespace {
+
+// A quick and dirty scope guard implementation
+class ExitGuard {
+public:
+ template<class Callable>
+ ExitGuard(Callable && undo_func) : f(std::forward<Callable>(undo_func)) {}
+ ExitGuard(ExitGuard && other) : f(std::move(other.f)) {
+ other.f = nullptr;
+ }
+
+ ~ExitGuard() {
+ run();
+ }
+
+ void run() noexcept {
+ if(f) { f(); f = nullptr; }
+ }
+
+ void abort() {
+ f = nullptr;
+ }
+
+ ExitGuard(const ExitGuard&) = delete;
+ void operator= (const ExitGuard&) = delete;
+
+private:
+ std::function<void()> f;
+};
+
+// A small helper to purge passwords. Paranoiac's note: this is not enough to guarantee the
+// complete purge of the password and all its copy from memory (ioslaves are sending the passwords
+// via dcop, so it's far beyond calling it "secure" in any way), but it's still better than nothing.
+void purgeString(TQString &s) {
+ s.fill('\0');
+ s.setLength(0);
+ s = TQString::null;
+}
+
+// A helper class to cleanup password when it goes out of the scope
+class PasswordPurger: public ExitGuard {
+public:
+ PasswordPurger(TQString &pw) : ExitGuard( [&pw](){purgeString(pw);} ) {}
+};
+
+} /* namespace */
+
// The callback function for libssh
int auth_callback(const char *prompt, char *buf, size_t len,
int echo, int verify, void *userdata)
@@ -120,6 +172,68 @@ void log_callback(ssh_session session, int priority, const char *message,
slave->log_callback(session, priority, message, userdata);
}
+class PublicKeyAuth: public SSHAuthMethod {
+public:
+ unsigned flag() override {return SSH_AUTH_METHOD_PUBLICKEY;};
+ int authenticate(sftpProtocol *ioslave) const override {
+ return ioslave->authenticatePublicKey();
+ }
+ SSHAuthMethod* clone() override {return new PublicKeyAuth; }
+};
+
+class KeyboardInteractiveAuth: public SSHAuthMethod {
+public:
+ KeyboardInteractiveAuth(bool noPaswordQuery = false): mNoPaswordQuery(noPaswordQuery) {}
+
+ unsigned flag() override {return SSH_AUTH_METHOD_INTERACTIVE;};
+ int authenticate(sftpProtocol *ioslave) const override {
+ return ioslave->authenticateKeyboardInteractive(mNoPaswordQuery);
+ }
+ SSHAuthMethod* clone() override {return new KeyboardInteractiveAuth(mNoPaswordQuery); }
+
+private:
+ const bool mNoPaswordQuery;
+};
+
+class PasswordAuth: public SSHAuthMethod {
+public:
+ PasswordAuth(bool noPaswordQuery = false): mNoPaswordQuery(noPaswordQuery) {}
+
+ unsigned flag() override {return SSH_AUTH_METHOD_PASSWORD;};
+ int authenticate(sftpProtocol *ioslave) const override {
+ return ioslave->authenticatePassword(mNoPaswordQuery);
+ }
+ SSHAuthMethod* clone() override {return new PasswordAuth(mNoPaswordQuery); }
+
+private:
+ const bool mNoPaswordQuery;
+};
+
+TQString SSHAuthMethod::flagToStr (unsigned m) {
+ switch (m) {
+ case SSH_AUTH_METHOD_NONE : return TQString::fromLatin1 ( "none" );
+ case SSH_AUTH_METHOD_PASSWORD : return TQString::fromLatin1 ( "password" );
+ case SSH_AUTH_METHOD_PUBLICKEY : return TQString::fromLatin1 ( "publickey" );
+ case SSH_AUTH_METHOD_HOSTBASED : return TQString::fromLatin1 ( "hostbased" );
+ case SSH_AUTH_METHOD_INTERACTIVE : return TQString::fromLatin1 ( "keyboard-interactive" );
+ case SSH_AUTH_METHOD_GSSAPI_MIC : return TQString::fromLatin1 ( "gssapi-with-mic" );
+ default : return TQString::fromLatin1 ( "unknown" );
+ }
+}
+
+TQStringList SSHAuthMethod::bitsetToStr (unsigned m) {
+ TQStringList rv;
+
+ for (int i=0; m>>i; i++) {
+ unsigned flag = m & (1 << i);
+ if (flag) {
+ rv.append(flagToStr(flag));
+ }
+ }
+ return rv;
+}
+
+
// Public key authentication
int sftpProtocol::auth_callback(const char *prompt, char *buf, size_t len,
int echo, int verify, void *userdata)
@@ -128,42 +242,82 @@ int sftpProtocol::auth_callback(const char *prompt, char *buf, size_t len,
(void) echo;
(void) verify;
(void) userdata;
+ (void) prompt;
+
+ Q_ASSERT(len>0);
kdDebug(TDEIO_SFTP_DB) << "Entering public key authentication callback" << endl;
- if(!pubKeyInfo)
- {
- pubKeyInfo = new TDEIO::AuthInfo;
- }
- else
- {
- // TODO: inform user about incorrect password
- }
+ int rc=0;
+ bool firstTimeCalled = !mPubKeyAuthData.wasCalled;
+ mPubKeyAuthData.wasCalled = true;
- pubKeyInfo->url.setProtocol("sftp");
- pubKeyInfo->url.setHost(mHost);
- pubKeyInfo->url.setPort(mPort);
- pubKeyInfo->url.setUser(mUsername);
+ AuthInfo pubKeyInfo = authInfo();
- pubKeyInfo->caption = i18n("SFTP Login");
- pubKeyInfo->comment = "sftp://" + mUsername + "@" + mHost;
- pubKeyInfo->username = mUsername;
- pubKeyInfo->readOnly = false;
- pubKeyInfo->prompt = TQString::fromUtf8(prompt);
- pubKeyInfo->keepPassword = false; // don't save passwords for public key,
+ pubKeyInfo.keepPassword = false; // don't save passwords for public key,
// that's the task of ssh-agent.
+ pubKeyInfo.readOnly = true; // We don't want to handle user name change when authing with a key
- if (!openPassDlg(*pubKeyInfo)) {
- kdDebug(TDEIO_SFTP_DB) << "User canceled entry of public key password." << endl;
- return -1;
+ TQString errMsg;
+ TQString keyFile;
+#if LIBSSH_VERSION_INT < SSH_VERSION_INT(0, 10, 0)
+ // no way to determine keyfile name on older libssh
+#else
+ char *ssh_key_file = 0;
+ rc = ssh_userauth_publickey_auto_get_current_identity(mSession, &ssh_key_file);
+
+ if (rc == 0 && ssh_key_file && ssh_key_file[0]) {
+ keyFile = ssh_key_file;
}
+ ssh_string_free_char(ssh_key_file);
+#endif
- strncpy(buf, pubKeyInfo->password.utf8().data(), len - 1);
+ bool firstTry = !mPubKeyAuthData.attemptedKeys.contains(keyFile);
- pubKeyInfo->password.fill('x');
- pubKeyInfo->password = "";
+ if (firstTry) {
+ SlaveBase::s_seqNr = mPubKeyAuthData.current_seqNr;
+ } else {
+ errMsg = i18n("Incorrect or invalid passphrase.").append('\n');
+ }
- return 0;
+ // libssh prompt is trash and we know we use this function only for publickey auth, so we'll give
+ // the user a descent prompt
+ if (!keyFile.isEmpty()) {
+ pubKeyInfo.prompt = i18n("Please enter the passphrase for next public key:\n%1").arg(keyFile);
+ } else { // Generally shouldn't happend but on older libssh
+ pubKeyInfo.prompt = i18n("Please enter the passphrase for your public key.");
+ }
+
+ // We don't want to clobber with normal passwords in kpasswdserver's cache
+ pubKeyInfo.realmValue = "keyfile passphrase:" + keyFile;
+
+ if (openPassDlg(pubKeyInfo, errMsg)) {
+ if (len < pubKeyInfo.password.utf8().length()+1) {
+ kdDebug(TDEIO_SFTP_DB) << "Insufficient buffer size for password: " << len
+ << " (" << pubKeyInfo.password.utf8().length()+1 << "needed)" << endl;
+ }
+
+ strncpy(buf, pubKeyInfo.password.utf8().data(), len-1);
+ buf[len-1]=0; // Just to be on the safe side
+
+ purgeString(pubKeyInfo.password);
+
+ // take a note that we already tried unlocking this keyfile
+ if(firstTry) {
+ mPubKeyAuthData.attemptedKeys.append(keyFile);
+ }
+
+ // we consider publickey auth canceled only if we cancel all the key dialogs
+ mPubKeyAuthData.wasCanceled = false;
+ } else {
+ kdDebug(TDEIO_SFTP_DB) << "User canceled entry of public key passphrase" << endl;
+ rc = -1;
+ if (firstTimeCalled) {
+ mPubKeyAuthData.wasCanceled = true;
+ }
+ }
+
+ return rc;
}
void sftpProtocol::log_callback(ssh_session session, int priority,
@@ -174,91 +328,323 @@ void sftpProtocol::log_callback(ssh_session session, int priority,
kdDebug(TDEIO_SFTP_DB) << "[" << priority << "] " << message << endl;
}
-int sftpProtocol::authenticateKeyboardInteractive(AuthInfo &info) {
- TQString name, instruction, prompt;
- int err = SSH_AUTH_ERROR;
+int sftpProtocol::authenticatePublicKey(){
+ kdDebug(TDEIO_SFTP_DB) << "Trying to authenticate with public key" << endl;
+
+ // First let's do some cleanup
+ mPubKeyAuthData.attemptedKeys.clear();
+ mPubKeyAuthData.current_seqNr = SlaveBase::s_seqNr;
+ mPubKeyAuthData.wasCalled = 0;
+ mPubKeyAuthData.wasCanceled = 0;
+
+ int rc;
+
+ while (1) {
+ mPubKeyAuthData.wasCalled = 0;
+ rc = ssh_userauth_publickey_auto(mSession, nullptr, nullptr);
+
+ kdDebug(TDEIO_SFTP_DB) << "ssh_userauth_publickey_auto returned rc=" << rc
+ << " ssh_err=" << ssh_get_error_code(mSession)
+ << " (" << ssh_get_error(mSession) << ")" << endl;
+ if (rc == SSH_AUTH_DENIED) {
+ if (!mPubKeyAuthData.wasCalled) {
+ kdDebug(TDEIO_SFTP_DB) << "Passkey auth denied because it has no matching key" << endl;
+ break; /* rc == SSH_AUTH_DENIED */
+ } else if (mPubKeyAuthData.wasCanceled) {
+ kdDebug(TDEIO_SFTP_DB) << "Passkey auth denied because user canceled" << endl;
+ rc = sftpProtocol::SSH_AUTH_CANCELED;
+ break;
+ } else {
+ kdDebug(TDEIO_SFTP_DB) << "User entered wrong passphrase for the key" << endl;
+ mPubKeyAuthData.current_seqNr = SlaveBase::s_seqNr;
+ // Try it again
+ }
+ } else {
+ // every other rc is either error or success
+ break;
+ }
+ }
+
+ return rc;
+}
+int sftpProtocol::authenticateKeyboardInteractive(bool noPaswordQuery) {
kdDebug(TDEIO_SFTP_DB) << "Entering keyboard interactive function" << endl;
- err = ssh_userauth_kbdint(mSession, mUsername.utf8().data(), NULL);
- while (err == SSH_AUTH_INFO) {
+ int rc = SSH_AUTH_ERROR;
+
+ bool retryDenied = false; // a flag to avoid infinite looping
+
+ TQString cachablePassword;
+ PasswordPurger cachePurger(cachablePassword);
+
+ // Different prompts during a single pass should be queried with the same s_seqNr value
+ long current_seqNr = SlaveBase::s_seqNr;
+
+ while (1) {
int n = 0;
int i = 0;
+ rc = ssh_userauth_kbdint(mSession, NULL, NULL);
+
+ if (rc == SSH_AUTH_DENIED) { // do nothing
+ kdDebug(TDEIO_SFTP_DB) << "kb-interactive auth was denied; retrying again" << endl;
+ if (retryDenied) {
+ // If we were denied update the s_seqNr
+ current_seqNr = SlaveBase::s_seqNr;
+ continue;
+ } else {
+ break;
+ }
+ } else if (rc != SSH_AUTH_INFO) {
+ kdDebug(TDEIO_SFTP_DB) << "Finishing kb-interactive auth rc=" << rc
+ << " ssh_err=" << ssh_get_error_code(mSession)
+ << " (" << ssh_get_error(mSession) << ")" << endl;
+ break;
+ }
+
+ // See "RFC4256 Section 3.3 User Interface" for meaning of the values
+ TQString name, instruction, prompt;
name = TQString::fromUtf8(ssh_userauth_kbdint_getname(mSession));
instruction = TQString::fromUtf8(ssh_userauth_kbdint_getinstruction(mSession));
n = ssh_userauth_kbdint_getnprompts(mSession);
+ if (n>0) {
+ // If there is at least one prompt we will want to retry auth if we fail
+ retryDenied = true;
+ }
+
kdDebug(TDEIO_SFTP_DB) << "name=" << name << " instruction=" << instruction
- << " prompts" << n << endl;
+ << " prompts:" << n << endl;
for (i = 0; i < n; ++i) {
char echo;
- const char *answer = "";
+ bool isPassword=false;
+ TQString answer;
+ TQString errMsg;
+
+ // restore the s_seqNr so it would be the same for all the prompts
+ SlaveBase::s_seqNr = current_seqNr;
prompt = TQString::fromUtf8(ssh_userauth_kbdint_getprompt(mSession, i, &echo));
kdDebug(TDEIO_SFTP_DB) << "prompt=" << prompt << " echo=" << TQString::number(echo) << endl;
- if (echo) {
- // See RFC4256 Section 3.3 User Interface
- TQString newPrompt;
- TDEIO::AuthInfo infoKbdInt;
- infoKbdInt.url.setProtocol("sftp");
- infoKbdInt.url.setHost(mHost);
- infoKbdInt.url.setPort(mPort);
+ TDEIO::AuthInfo infoKbdInt = authInfo();
+ infoKbdInt.realmValue = prompt; // each prompt will be treated on its own by kpasswdserver
+ infoKbdInt.keepPassword = false;
+
+ if (!name.isEmpty()) {
+ infoKbdInt.caption = TQString(i18n("SFTP Login") + " - " + name);
+ }
+
+ // Those strings might or might not contain some sensitive information
+ PasswordPurger answerPurger{answer};
+ PasswordPurger infoPurger{infoKbdInt.password};
- infoKbdInt.caption = i18n("SFTP Login");
- infoKbdInt.comment = "sftp://" + mUsername + "@" + mHost;
+ if (!echo) {
+ // ssh server requests us to ask user a question without displaying an answer. In normal
+ // circumstances this is probably a password, but it might be something else depending
+ // on the server configuration.
+ if (prompt.lower().startsWith("password")) {
+ // We can assume that the ssh server asks for a password and we will handle that case
+ // with more care since it's what most users will see
+ isPassword = true;
+ if (noPaswordQuery) { // if we have a cached password we might use it
+ kdDebug(TDEIO_SFTP_DB) << "Using cached password" << endl;
+ answer = mPassword;
+ cachablePassword = mPassword;
+ purgeString(mPassword); // if we used up password purge it
+ } else {
+ infoKbdInt.prompt = i18n("Please enter your password.");
+ infoKbdInt.realmValue = TQString(); // passwords use generic realm
+ infoKbdInt.keepPassword = true;
+
+ if (mPasswordWasPrompted) {
+ errMsg = i18n("Login failed: incorrect password or username.").append('\n');
+ }
+ mPasswordWasPrompted = true;
+ }
+ } else {
+ // If the server's request doesn't look like a password, keep the servers prompt but
+ // don't prompt for saving the answer
+ infoKbdInt.prompt = i18n("Please enter answer for the next request:");
+ if (!instruction.isEmpty()) {
+ infoKbdInt.prompt.append("\n\n").append(instruction);
+ }
+ infoKbdInt.prompt.append("\n\n").append(prompt);
+ infoKbdInt.readOnly = true; // set username readonly (enable changing it only with password)
+ }
+
+ if (answer.isNull()) {
+ if (openPassDlg(infoKbdInt, errMsg)) {
+ answer = infoKbdInt.password;
+ kdDebug(TDEIO_SFTP_DB) << "Got the answer from the password dialog" << endl;
- if (!name.isEmpty()) {
- infoKbdInt.caption = TQString(i18n("SFTP Login") + " - " + name);
+ if (isPassword) {
+ TQString sshUser=sshUsername();
+ if (infoKbdInt.username != sshUser) {
+ kdDebug(TDEIO_SFTP_DB) << "Username changed from " << sshUser
+ << " to " << infoKbdInt.username << endl;
+ mCachedUsername = infoKbdInt.username;
+ mPassword = infoKbdInt.password;
+
+ return sftpProtocol::SSH_AUTH_NEED_RECONNECT;
+ }
+ }
+ } else {
+ return sftpProtocol::SSH_AUTH_CANCELED;
+ }
}
+ } else {
+ // ssh server asks for some clear-text information from a user (e.g. a one-time
+ // identification code) which should be echoed while user enters it. As for now tdeio has
+ // no means to handle that correctly, so we will have to be creative with the password
+ // dialog.
+ TQString newPrompt;
if (!instruction.isEmpty()) {
newPrompt = instruction + "\n\n";
}
+ newPrompt.append(prompt).append("\n\n");
+ newPrompt.append(i18n("Use the username input field to answer this question."));
+ infoKbdInt.prompt = newPrompt;
- newPrompt.append(prompt + "\n\n");
- infoKbdInt.readOnly = false;
- infoKbdInt.keepPassword = false;
- infoKbdInt.prompt = i18n("Use the username input field to answer this question.");
+ infoKbdInt.url.setUser(infoKbdInt.username);
+ infoKbdInt.username = TQString::null;
+ infoKbdInt.readOnly = false;
if (openPassDlg(infoKbdInt)) {
- kdDebug(TDEIO_SFTP_DB) << "Got the answer from the password dialog" << endl;
- answer = info.username.utf8().data();
+ answer = infoKbdInt.username;
+ kdDebug(TDEIO_SFTP_DB) << "Got the answer from the password dialog: " << answer << endl;
+ } else {
+ return sftpProtocol::SSH_AUTH_CANCELED;
}
+ }
- if (ssh_userauth_kbdint_setanswer(mSession, i, answer) < 0) {
- kdDebug(TDEIO_SFTP_DB) << "An error occurred setting the answer: "
- << ssh_get_error(mSession) << endl;
- return SSH_AUTH_ERROR;
- }
- break;
- } else {
- if (prompt.lower().startsWith("password")) {
- answer = mPassword.utf8().data();
- } else {
- info.readOnly = true; // set username readonly
- info.prompt = prompt;
+ if (ssh_userauth_kbdint_setanswer(mSession, i, answer.utf8().data()) < 0) {
+ kdDebug(TDEIO_SFTP_DB) << "An error occurred setting the answer: "
+ << ssh_get_error(mSession) << endl;
+ return SSH_AUTH_ERROR;
+ }
+ } // for each ssh_userauth_kbdint_getprompt()
+ } // while (1)
- if (openPassDlg(info)) {
- kdDebug(TDEIO_SFTP_DB) << "Got the answer from the password dialog" << endl;
- answer = info.password.utf8().data();
- }
- }
+ if (!mPasswordWasPrompted && !cachablePassword.isEmpty() && (rc == SSH_AUTH_SUCCESS || rc == SSH_AUTH_PARTIAL)) {
+ // if the password was never prompted, it was never cached, so we should cache it manually
+ TDEIO::AuthInfo info = authInfo();
+ info.password = cachablePassword;
+ info.keepPassword = false;
+ cacheAuthentication(info);
+ purgeString(info.password);
+ }
- if (ssh_userauth_kbdint_setanswer(mSession, i, answer) < 0) {
- kdDebug(TDEIO_SFTP_DB) << "An error occurred setting the answer: "
- << ssh_get_error(mSession) << endl;
- return SSH_AUTH_ERROR;
- }
+ return rc;
+}
+
+int sftpProtocol::authenticatePassword(bool noPaswordQuery) {
+ kdDebug(TDEIO_SFTP_DB) << "Trying to authenticate with password" << endl;
+
+ AuthInfo info = authInfo();
+ info.keepPassword = true;
+ info.prompt = i18n("Please enter your username and password.");
+
+ PasswordPurger pPurger(info.password);
+
+ int rc;
+ do {
+ TQString errMsg;
+
+ if(noPaswordQuery) { // on the first try use cached password
+ info.password = mPassword;
+ purgeString(mPassword);
+ } else {
+ if (mPasswordWasPrompted) {
+ errMsg = i18n("Login failed: incorrect password or username.").append('\n');
+ }
+
+ mPasswordWasPrompted = true;
+
+ // Handle user canceled or dialog failed to open...
+ if (!openPassDlg(info, errMsg)) {
+ kdDebug(TDEIO_SFTP_DB) << "User canceled password dialog" << endl;
+ return sftpProtocol::SSH_AUTH_CANCELED;
+ }
+
+ TQString sshUser=sshUsername();
+ if (info.username != sshUser) {
+ kdDebug(TDEIO_SFTP_DB) << "Username changed from " << sshUser
+ << " to " << info.username << endl;
+ mCachedUsername = info.username;
+ mPassword = info.password;
+ // libssh doc says that most servers don't permit changing the username during
+ // authentication, so we should reinitialize the session here
+ return sftpProtocol::SSH_AUTH_NEED_RECONNECT;
}
}
- err = ssh_userauth_kbdint(mSession, mUsername.utf8().data(), NULL);
+
+ rc = ssh_userauth_password(mSession, NULL, info.password.utf8().data());
+
+ } while (rc == SSH_AUTH_DENIED && !noPaswordQuery);
+
+ if (!mPasswordWasPrompted && (rc == SSH_AUTH_SUCCESS || rc == SSH_AUTH_PARTIAL)) {
+ // if the password was never prompted, it was never cached, so we should cache it manually
+ info.keepPassword = false;
+ cacheAuthentication(info);
+ }
+ return rc;
+}
+
+
+TQString sftpProtocol::sshUsername() {
+ int rc;
+ TQString rv;
+
+ char *ssh_username = NULL;
+ rc = ssh_options_get(mSession, SSH_OPTIONS_USER, &ssh_username);
+ if (rc == 0 && ssh_username && ssh_username[0]) {
+ rv = TQString::fromUtf8(ssh_username);
}
+ ssh_string_free_char(ssh_username);
- return err;
+ return rv;
+}
+
+
+TQString sftpProtocol::sshError(TQString errMsg) {
+ if (ssh_get_error_code(mSession)) {
+ errMsg.append("\n\n").append(i18n("SSH error: \"%1\" (%2)")
+ .arg(TQString::fromUtf8(ssh_get_error(mSession))).arg(ssh_get_error_code(mSession)));
+ }
+ return errMsg;
+}
+
+TDEIO::AuthInfo sftpProtocol::authInfo() {
+ TDEIO::AuthInfo rv;
+
+ rv.url.setProtocol("sftp");
+ rv.url.setHost(mHost);
+ rv.url.setPort(mPort);
+ rv.url.setUser(mUsername);
+
+ rv.caption = i18n("SFTP Login");
+ rv.comment = "sftp://" + mHost + ':' + TQString::number(mPort);
+ rv.commentLabel = i18n("site:");
+
+ if(!mUsername.isEmpty()) {
+ rv.username = mUsername;
+ } if(!mCachedUsername.isEmpty()) {
+ rv.username = mCachedUsername;
+ } else if (mSession) {
+ rv.username = sshUsername();
+ }
+
+ // if username was specified in the address string it shouldn't be changed
+ if (!mUsername.isEmpty()) {
+ rv.readOnly = true;
+ }
+
+ return rv;
}
void sftpProtocol::reportError(const KURL &url, const int err) {
@@ -447,7 +833,7 @@ TQString sftpProtocol::canonicalizePath(const TQString &path) {
sftpProtocol::sftpProtocol(const TQCString &pool_socket, const TQCString &app_socket)
: SlaveBase("tdeio_sftp", pool_socket, app_socket),
mConnected(false), mPort(-1), mSession(NULL), mSftp(NULL) {
-#ifndef Q_WS_WIN
+#ifndef TQ_WS_WIN
kdDebug(TDEIO_SFTP_DB) << "pid = " << getpid() << endl;
kdDebug(TDEIO_SFTP_DB) << "debug = " << getenv("TDEIO_SFTP_LOG_VERBOSITY") << endl;
@@ -470,15 +856,17 @@ sftpProtocol::sftpProtocol(const TQCString &pool_socket, const TQCString &app_so
}
sftpProtocol::~sftpProtocol() {
-#ifndef Q_WS_WIN
+#ifndef TQ_WS_WIN
kdDebug(TDEIO_SFTP_DB) << "pid = " << getpid() << endl;
#endif
closeConnection();
- delete mCallbacks;
+ free(mCallbacks);
/* cleanup and shut down cryto stuff */
ssh_finalize();
+
+ purgeString(mPassword);
}
void sftpProtocol::setHost(const TQString& h, int port, const TQString& user, const TQString& pass) {
@@ -505,53 +893,11 @@ void sftpProtocol::setHost(const TQString& h, int port, const TQString& user, co
mUsername = user;
mPassword = pass;
+ mCachedUsername = TQString::null;
}
-void sftpProtocol::openConnection() {
-
- if (mConnected) {
- return;
- }
-
- kdDebug(TDEIO_SFTP_DB) << "username=" << mUsername << ", host=" << mHost << ", port=" << mPort << endl;
-
- infoMessage(i18n("Opening SFTP connection to host %1:%2").arg(mHost).arg(mPort));
-
- if (mHost.isEmpty()) {
- kdDebug(TDEIO_SFTP_DB) << "openConnection(): Need hostname..." << endl;
- error(TDEIO::ERR_UNKNOWN_HOST, i18n("No hostname specified."));
- return;
- }
-
- // Setup AuthInfo for use with password caching and the
- // password dialog box.
- AuthInfo info;
-
- info.url.setProtocol("sftp");
- info.url.setHost(mHost);
- info.url.setPort(mPort);
- info.url.setUser(mUsername);
- info.caption = i18n("SFTP Login");
- info.comment = "sftp://" + mHost + ':' + TQString::number(mPort);
- info.commentLabel = i18n("site:");
- info.username = mUsername;
- info.keepPassword = true; // make the "keep Password" check box visible to the user.
-
- // Check for cached authentication info if no password is specified...
- if (mPassword.isEmpty()) {
- kdDebug(TDEIO_SFTP_DB) << "checking cache: info.username = " << info.username
- << ", info.url = " << info.url.prettyURL() << endl;
-
- if (checkCachedAuthentication(info)) {
- kdDebug() << "using cached" << endl;
- mUsername = info.username;
- mPassword = info.password;
- }
- }
- // Start the ssh connection.
- TQString msg; // msg for dialog box
- TQString caption; // dialog box caption
+int sftpProtocol::initializeConnection() {
unsigned char *hash = NULL; // the server hash
char *hexa;
char *verbosity;
@@ -561,7 +907,7 @@ void sftpProtocol::openConnection() {
mSession = ssh_new();
if (mSession == NULL) {
error(TDEIO::ERR_INTERNAL, i18n("Could not create a new SSH session."));
- return;
+ return SSH_ERROR;
}
kdDebug(TDEIO_SFTP_DB) << "Creating the SSH session and setting options" << endl;
@@ -591,23 +937,24 @@ void sftpProtocol::openConnection() {
rc = ssh_options_set(mSession, SSH_OPTIONS_HOST, mHost.utf8().data());
if (rc < 0) {
error(TDEIO::ERR_OUT_OF_MEMORY, i18n("Could not set host."));
- return;
+ return SSH_ERROR;
}
if (mPort > 0) {
rc = ssh_options_set(mSession, SSH_OPTIONS_PORT, &mPort);
if (rc < 0) {
- error(TDEIO::ERR_OUT_OF_MEMORY, i18n("Could not set port."));
- return;
+ error(TDEIO::ERR_OUT_OF_MEMORY, i18n("Could not set port."));
+ return SSH_ERROR;
}
}
// Set the username
- if (!mUsername.isEmpty()) {
- rc = ssh_options_set(mSession, SSH_OPTIONS_USER, mUsername.utf8().data());
+ if (!mCachedUsername.isEmpty() || !mUsername.isEmpty()) {
+ TQString username = !mCachedUsername.isEmpty() ? mCachedUsername : mUsername;
+ rc = ssh_options_set(mSession, SSH_OPTIONS_USER, username.utf8().data());
if (rc < 0) {
error(TDEIO::ERR_OUT_OF_MEMORY, i18n("Could not set username."));
- return;
+ return rc;
}
}
@@ -616,7 +963,7 @@ void sftpProtocol::openConnection() {
rc = ssh_options_set(mSession, SSH_OPTIONS_LOG_VERBOSITY_STR, verbosity);
if (rc < 0) {
error(TDEIO::ERR_OUT_OF_MEMORY, i18n("Could not set log verbosity."));
- return;
+ return rc;
}
}
@@ -624,7 +971,7 @@ void sftpProtocol::openConnection() {
rc = ssh_options_parse_config(mSession, NULL);
if (rc < 0) {
error(TDEIO::ERR_INTERNAL, i18n("Could not parse the config file."));
- return;
+ return rc;
}
ssh_set_callbacks(mSession, mCallbacks);
@@ -634,9 +981,8 @@ void sftpProtocol::openConnection() {
/* try to connect */
rc = ssh_connect(mSession);
if (rc < 0) {
- error(TDEIO::ERR_COULD_NOT_CONNECT, TQString::fromUtf8(ssh_get_error(mSession)));
- closeConnection();
- return;
+ error(TDEIO::ERR_COULD_NOT_CONNECT, sshError());
+ return rc;
}
kdDebug(TDEIO_SFTP_DB) << "Getting the SSH server hash" << endl;
@@ -644,24 +990,24 @@ void sftpProtocol::openConnection() {
/* get the hash */
ssh_key serverKey;
#if LIBSSH_VERSION_INT < SSH_VERSION_INT(0, 7, 90)
- if (ssh_get_publickey(mSession, &serverKey) < 0) {
+ rc = ssh_get_publickey(mSession, &serverKey);
#else
- if (ssh_get_server_publickey(mSession, &serverKey) < 0) {
+ rc = ssh_get_server_publickey(mSession, &serverKey);
#endif
- error(TDEIO::ERR_COULD_NOT_CONNECT, TQString::fromUtf8(ssh_get_error(mSession)));
- closeConnection();
- return;
+ if (rc<0) {
+ error(TDEIO::ERR_COULD_NOT_CONNECT, sshError());
+ return rc;
}
size_t hlen;
#if LIBSSH_VERSION_INT < SSH_VERSION_INT(0, 8, 90)
- if (ssh_get_publickey_hash(serverKey, SSH_PUBLICKEY_HASH_MD5, &hash, &hlen) < 0) {
+ rc = ssh_get_publickey_hash(serverKey, SSH_PUBLICKEY_HASH_MD5, &hash, &hlen);
#else
- if (ssh_get_publickey_hash(serverKey, SSH_PUBLICKEY_HASH_SHA256, &hash, &hlen) < 0) {
+ rc = ssh_get_publickey_hash(serverKey, SSH_PUBLICKEY_HASH_SHA256, &hash, &hlen);
#endif
- error(TDEIO::ERR_COULD_NOT_CONNECT, TQString::fromUtf8(ssh_get_error(mSession)));
- closeConnection();
- return;
+ if (rc<0) {
+ error(TDEIO::ERR_COULD_NOT_CONNECT, sshError());
+ return rc;
}
kdDebug(TDEIO_SFTP_DB) << "Checking if the SSH server is known" << endl;
@@ -682,8 +1028,7 @@ void sftpProtocol::openConnection() {
"An attacker might change the default server key to confuse your "
"client into thinking the key does not exist.\n"
"Please contact your system administrator.\n%1").arg(TQString::fromUtf8(ssh_get_error(mSession))));
- closeConnection();
- return;
+ return SSH_ERROR;
case TDEIO_SSH_KNOWN_HOSTS_CHANGED:
hexa = ssh_get_hexa(hash, hlen);
delete hash;
@@ -695,10 +1040,11 @@ void sftpProtocol::openConnection() {
"Please contact your system administrator.\n%3").arg(
mHost).arg(TQString::fromUtf8(hexa)).arg(TQString::fromUtf8(ssh_get_error(mSession))));
delete hexa;
- closeConnection();
- return;
+ return SSH_ERROR;
case TDEIO_SSH_KNOWN_HOSTS_NOT_FOUND:
- case TDEIO_SSH_KNOWN_HOSTS_UNKNOWN:
+ case TDEIO_SSH_KNOWN_HOSTS_UNKNOWN: {
+ TQString msg; // msg for dialog box
+ TQString caption; // dialog box caption
hexa = ssh_get_hexa(hash, hlen);
delete hash;
caption = i18n("Warning: Cannot verify host's identity.");
@@ -708,9 +1054,8 @@ void sftpProtocol::openConnection() {
delete hexa;
if (KMessageBox::Yes != messageBox(WarningYesNo, msg, caption)) {
- closeConnection();
error(TDEIO::ERR_USER_CANCELED, TQString());
- return;
+ return SSH_ERROR;
}
/* write the known_hosts file */
@@ -720,136 +1065,200 @@ void sftpProtocol::openConnection() {
#else
if (ssh_session_update_known_hosts(mSession) != SSH_OK) {
#endif
- error(TDEIO::ERR_USER_CANCELED, TQString::fromUtf8(ssh_get_error(mSession)));
- closeConnection();
- return;
+ error(TDEIO::ERR_USER_CANCELED, sshError());
+ return SSH_ERROR;
}
break;
+ }
case TDEIO_SSH_KNOWN_HOSTS_ERROR:
delete hash;
- error(TDEIO::ERR_COULD_NOT_CONNECT, TQString::fromUtf8(ssh_get_error(mSession)));
- return;
+ error(TDEIO::ERR_COULD_NOT_CONNECT, sshError());
+ return SSH_ERROR;
}
kdDebug(TDEIO_SFTP_DB) << "Trying to authenticate with the server" << endl;
- // Try to authenticate
- rc = ssh_userauth_none(mSession, NULL);
- if (rc == SSH_AUTH_ERROR) {
- closeConnection();
- error(TDEIO::ERR_COULD_NOT_LOGIN, i18n("Authentication failed (method: %1).")
- .arg(i18n("none")));
+ return SSH_OK;
+}
+
+
+void sftpProtocol::openConnection() {
+
+ if (mConnected) {
return;
}
- int method = ssh_auth_list(mSession);
- if (!method && rc != SSH_AUTH_SUCCESS)
- {
- error(TDEIO::ERR_COULD_NOT_LOGIN, i18n("Authentication failed."
- " The server did not send any authentication methods!"));
+ kdDebug(TDEIO_SFTP_DB) << "username=" << mUsername << ", host=" << mHost << ", port=" << mPort << endl;
+
+ infoMessage(i18n("Opening SFTP connection to host %1:%2").arg(mHost).arg(mPort));
+
+ if (mHost.isEmpty()) {
+ kdDebug(TDEIO_SFTP_DB) << "openConnection(): Need hostname..." << endl;
+ error(TDEIO::ERR_UNKNOWN_HOST, i18n("No hostname specified."));
return;
}
- bool firstTime = true;
- bool dlgResult;
- while (rc != SSH_AUTH_SUCCESS) {
- // Try to authenticate with public key first
- if (rc != SSH_AUTH_SUCCESS && (method & SSH_AUTH_METHOD_PUBLICKEY) && !mPassword)
- {
- kdDebug(TDEIO_SFTP_DB) << "Trying to authenticate with public key" << endl;
- for(;;)
- {
- rc = ssh_userauth_publickey_auto(mSession, nullptr, nullptr);
- if (rc == SSH_AUTH_ERROR)
- {
- clearPubKeyAuthInfo();
- closeConnection();
- error(TDEIO::ERR_COULD_NOT_LOGIN, i18n("Authentication failed (method: %1).")
- .arg(i18n("public key")));
- return;
- }
- if (rc == SSH_AUTH_DENIED || !pubKeyInfo || !pubKeyInfo->isModified())
- {
- clearPubKeyAuthInfo();
- break;
- }
- }
+ // Check for cached authentication info if no password is specified...
+ if (mPassword.isEmpty()) {
+ AuthInfo info = authInfo();
+
+ kdDebug(TDEIO_SFTP_DB) << "checking cache: info.username = " << info.username
+ << ", info.url = " << info.url.prettyURL() << endl;
+
+ if (checkCachedAuthentication(info)) {
+ kdDebug() << "using cached" << endl;
+ mCachedUsername = info.username;
+ mPassword = info.password;
+
+ purgeString(info.password); //< not really necessary because of Qt's implicit data sharing
}
+ }
- // Try to authenticate with keyboard interactive
- if (rc != SSH_AUTH_SUCCESS && (method & SSH_AUTH_METHOD_INTERACTIVE))
- {
- kdDebug(TDEIO_SFTP_DB) << "Trying to authenticate with keyboard interactive" << endl;
+ mPasswordWasPrompted = false;
+ PasswordPurger pwPurger{mPassword};
- TDEIO::AuthInfo tmpInfo(info);
- rc = authenticateKeyboardInteractive(tmpInfo);
- if (rc == SSH_AUTH_SUCCESS)
- {
- info = tmpInfo;
- }
- else if (rc == SSH_AUTH_ERROR)
- {
- closeConnection();
- error(TDEIO::ERR_COULD_NOT_LOGIN, i18n("Authentication failed (method: %1).")
- .arg(i18n("keyboard interactive")));
- return;
- }
- }
+ int rc;
+ ExitGuard connectionCloser([this](){ closeConnection(); });
- // Try to authenticate with password
- if (rc != SSH_AUTH_SUCCESS && (method & SSH_AUTH_METHOD_PASSWORD))
- {
- kdDebug(TDEIO_SFTP_DB) << "Trying to authenticate with password" << endl;
-
- info.keepPassword = true;
- for(;;)
- {
- if(!firstTime || mPassword.isEmpty())
- {
- if (firstTime) {
- info.prompt = i18n("Please enter your username and password.");
- } else {
- info.prompt = i18n("Login failed.\nPlease confirm your username and password, and enter them again.");
- }
- dlgResult = openPassDlg(info);
-
- // Handle user canceled or dialog failed to open...
- if (!dlgResult) {
- kdDebug(TDEIO_SFTP_DB) << "User canceled, dlgResult = " << dlgResult << endl;
- closeConnection();
- error(TDEIO::ERR_USER_CANCELED, TQString());
- return;
- }
+ do { // A loop to restart connection when needed
+ // Start the ssh connection.
+ if (initializeConnection() < 0) {
+ return;
+ }
- firstTime = false;
- }
+ // Try to authenticate (this required before calling ssh_auth_list())
+ rc = ssh_userauth_none(mSession, NULL);
+ if (rc == SSH_AUTH_ERROR) {
+ error(TDEIO::ERR_COULD_NOT_LOGIN, sshError(i18n("Authentication failed (method: %1).")
+ .arg(i18n("none"))));
+ return;
+ }
- if (mUsername != info.username) {
- kdDebug(TDEIO_SFTP_DB) << "Username changed from " << mUsername
- << " to " << info.username << endl;
- }
- mUsername = info.username;
- mPassword = info.password;
+ // Preinit the list of supported auth methods
+ static const auto authMethodsNormal = [](){
+ std::vector<std::unique_ptr<SSHAuthMethod>> rv;
+ rv.emplace_back(std::unique_ptr<PublicKeyAuth>(new PublicKeyAuth));
+ rv.emplace_back(std::unique_ptr<KeyboardInteractiveAuth>(new KeyboardInteractiveAuth));
+ rv.emplace_back(std::unique_ptr<PasswordAuth>(new 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 std::unique_ptr<SSHAuthMethod> &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);
+
+ SlaveBase::s_seqNr = current_seqNr;
+
+ if (!availableMethodes) {
+ // Technically libssh docs suggest that the server merely MAY send auth methods, but it's
+ // highly unclear what we should do in such case and it looks like openssh doesn't have an
+ // 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;
+ }
- rc = ssh_userauth_password(mSession, mUsername.utf8().data(),
- mPassword.utf8().data());
- if (rc == SSH_AUTH_ERROR) {
- closeConnection();
- error(TDEIO::ERR_COULD_NOT_LOGIN, i18n("Authentication failed (method: %1).")
- .arg(i18n("password")));
+ const auto *authMethods = &authMethodsNormal;
+
+ // If we have cached password we want try to use it before public key
+ if(!mPassword.isEmpty()) {
+ static const auto authMethodsWithPassword = []() {
+ std::vector<std::unique_ptr<SSHAuthMethod>> rv;
+ rv.emplace_back(std::unique_ptr<KeyboardInteractiveAuth>(
+ new KeyboardInteractiveAuth(/* noPasswordQuery = */true) ) );
+ rv.emplace_back(std::unique_ptr<PasswordAuth>(
+ new PasswordAuth(/* noPasswordQuery = */true) ) );
+ for (const auto &m: authMethodsNormal) { rv.emplace_back(m->clone()); }
+ return rv;
+ }();
+
+ authMethods = &authMethodsWithPassword;
+ }
+
+ // Actually iterate over the list of methods and try them out
+ for (const auto &method: *authMethods) {
+ if (!(availableMethodes & method->flag())) { continue; }
+
+ rc = method->authenticate( this );
+ attemptedMethods |= method->flag();
+ if (rc == SSH_AUTH_SUCCESS || rc == SSH_AUTH_PARTIAL) {
+ kdDebug(TDEIO_SFTP_DB) << "method=" << method->name() << ": auth "
+ << (rc == SSH_AUTH_SUCCESS ? "success" : "partial") << endl;
+ break; // either next auth method or continue on with the connect
+ } else if (rc == SSH_AUTH_ERROR || rc == SSH_AUTH_AGAIN) {
+ 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
+ // in case there were too many failed authentication attempts
+ if (rc == SSH_AUTH_AGAIN) {
+ 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_SUCCESS) {
+ } 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;
}
}
- }
- }
+
+ // 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;
+ } 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)
+ } while(rc == SSH_AUTH_NEED_RECONNECT);
+
// start sftp session
kdDebug(TDEIO_SFTP_DB) << "Trying to request the sftp session" << endl;
mSftp = sftp_new(mSession);
if (mSftp == NULL) {
- closeConnection();
error(TDEIO::ERR_COULD_NOT_LOGIN, i18n("Unable to request the SFTP subsystem. "
"Make sure SFTP is enabled on the server."));
return;
@@ -857,34 +1266,19 @@ void sftpProtocol::openConnection() {
kdDebug(TDEIO_SFTP_DB) << "Trying to initialize the sftp session" << endl;
if (sftp_init(mSftp) < 0) {
- closeConnection();
error(TDEIO::ERR_COULD_NOT_LOGIN, i18n("Could not initialize the SFTP session."));
return;
}
// Login succeeded!
infoMessage(i18n("Successfully connected to %1").arg(mHost));
- info.url.setProtocol("sftp");
- info.url.setHost(mHost);
- info.url.setPort(mPort);
- info.url.setUser(mUsername);
- info.username = mUsername;
- info.password = mPassword;
-
- kdDebug(TDEIO_SFTP_DB) << "Caching info.username = " << info.username
- << ", info.url = " << info.url.prettyURL() << endl;
-
- cacheAuthentication(info);
//setTimeoutSpecialCommand(TDEIO_SFTP_SPECIAL_TIMEOUT);
mConnected = true;
- connected();
+ connectionCloser.abort();
- mPassword.fill('x');
- mPassword = "";
- info.password.fill('x');
- info.password = "";
+ connected();
return;
}
@@ -925,7 +1319,7 @@ void sftpProtocol::special(const TQByteArray &data) {
}
if (rc < 0) {
- kdDebug(TDEIO_SFTP_DB) << "channel_poll failed: " << ssh_get_error(mSession);
+ kdDebug(TDEIO_SFTP_DB) << "channel_poll failed: " << ssh_get_error(mSession) << endl;
}
setTimeoutSpecialCommand(TDEIO_SFTP_SPECIAL_TIMEOUT);
@@ -1833,12 +2227,3 @@ void sftpProtocol::slave_status() {
kdDebug(TDEIO_SFTP_DB) << "connected to " << mHost << "?: " << mConnected << endl;
slaveStatus((mConnected ? mHost : TQString()), mConnected);
}
-
-void sftpProtocol::clearPubKeyAuthInfo()
-{
- if (!pubKeyInfo)
- {
- delete pubKeyInfo;
- pubKeyInfo = nullptr;
- }
-}