You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
tdebase/tdeioslave/sftp/tdeio_sftp.cpp

2226 lines
66 KiB

/*
* Copyright (c) 2001 Lucas Fisher <ljfisher@purdue.edu>
* Copyright (c) 2009 Andreas Schneider <mail@cynapses.org>
* Copyright (c) 2020 Martin Sandsmark <martin@sandsmark.ninja>
* KDE2 port
* Copyright (c) 2022 Mavridis Philippe <mavridisf@gmail.com>
* Trinity port
*
* Portions Copyright (c) 2020-2021 Harald Sitter <sitter@kde.org>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License (LGPL) as published by the Free Software Foundation;
* either version 2 of the License, or (at your option) any later
* version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public License
* along with this library; see the file COPYING.LIB. If not, write to
* the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#include "tdeio_sftp.h"
#include <fcntl.h>
#include <tqapplication.h>
#include <tqfile.h>
#include <tqdir.h>
#include <numeric>
#include <functional>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <time.h>
#include <string.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <sys/time.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <tdeapplication.h>
#include <kdebug.h>
#include <tdemessagebox.h>
#include <tdeglobal.h>
#include <kstandarddirs.h>
#include <tdelocale.h>
#include <kurl.h>
#include <tdeio/ioslave_defaults.h>
#include <kmimetype.h>
#include <kmimemagic.h>
#include <signal.h>
#include <libssh/libssh.h>
#include <libssh/sftp.h>
#include <libssh/callbacks.h>
#define TDEIO_SFTP_SPECIAL_TIMEOUT 30
#define ZERO_STRUCTP(x) do { if ((x) != NULL) memset((char *)(x), 0, sizeof(*(x))); } while(0)
using namespace TDEIO;
extern "C"
{
int KDE_EXPORT kdemain( int argc, char **argv )
{
TDEInstance instance( "tdeio_sftp" );
kdDebug(TDEIO_SFTP_DB) << "*** Starting tdeio_sftp " << endl;
if (argc != 4) {
kdDebug(TDEIO_SFTP_DB) << "Usage: tdeio_sftp protocol domain-socket1 domain-socket2" << endl;
exit(-1);
}
sftpProtocol slave(argv[2], argv[3]);
if (getenv("DEBUG_TDEIO_SFTP")) {
// Give us a coredump in the journal
signal(6, SIG_DFL);
}
slave.dispatchLoop();
kdDebug(TDEIO_SFTP_DB) << "*** tdeio_sftp Done" << endl;
return 0;
}
}
// 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)
{
if (userdata == NULL) {
return -1;
}
sftpProtocol *slave = (sftpProtocol *) userdata;
if (slave->auth_callback(prompt, buf, len, echo, verify, userdata) < 0) {
return -1;
}
return 0;
}
void log_callback(ssh_session session, int priority, const char *message,
void *userdata) {
if (userdata == NULL) {
return;
}
sftpProtocol *slave = (sftpProtocol *) userdata;
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)
{
// unused variables
(void) echo;
(void) verify;
(void) userdata;
(void) prompt;
Q_ASSERT(len>0);
kdDebug(TDEIO_SFTP_DB) << "Entering public key authentication callback" << endl;
int rc=0;
bool firstTimeCalled = !mPubKeyAuthData.wasCalled;
mPubKeyAuthData.wasCalled = true;
AuthInfo pubKeyInfo = authInfo();
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
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
bool firstTry = !mPubKeyAuthData.attemptedKeys.contains(keyFile);
tdeioslave/sftp: save/restore seqNr for multi-factor auth In case the server is set up for multi-factor authentication we could be have to query several things from the user like password, a key passphrase, their mother's maiden name etc. It doesn't make a big difference during an initial connection, but it butchers the reconnection process: it can retrieve the answer of the user to the first question (e.g. their password), but it fails to retrieve the second one (e.g. the key passphrase). So the user would be forced to reenter the answer for the second question upon each reconnection. The reason for this is the passwdserver's desig (see DESIGN [1]): Each query for AuthInfo with the openPassDlg() has an secNr number associated with it. If it's smaller than the one of the one stored for the privious request, than the one from the cache will be returned automagically, if it's bigger the dialog will be prompted to the user. Each call to openPassDlg() advances s_seqNr to the last value reported by the passwdserver. So the first call will return the cached value and subsequent calls will actually display the dialog to the user (assuming authentication with the cached data failed). But in case of multi-factor auth we have to query user for several independent values. And we want to try to retrieve each one of those from the cache. So we have to get a bit hacky and manually manipulate the SlaveBase::s_seqNr value. [1]: https://mirror.git.trinitydesktop.org/gitea/TDE/tdelibs/src/branch/master/tdeio/kpasswdserver/DESIGN Signed-off-by: Alexander Golubev <fatzer2@gmail.com> (cherry picked from commit 95b18e63382c4f0013c4eb2473d04f6020a84b7a)
10 months ago
if (firstTry) {
SlaveBase::s_seqNr = mPubKeyAuthData.current_seqNr;
} else {
errMsg = i18n("Incorrect or invalid passphrase.").append('\n');
}
// 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,
const char *message, void *userdata) {
(void) session;
(void) userdata;
kdDebug(TDEIO_SFTP_DB) << "[" << priority << "] " << message << endl;
}
int sftpProtocol::authenticatePublicKey(){
tdeioslave/sftp: save/restore seqNr for multi-factor auth In case the server is set up for multi-factor authentication we could be have to query several things from the user like password, a key passphrase, their mother's maiden name etc. It doesn't make a big difference during an initial connection, but it butchers the reconnection process: it can retrieve the answer of the user to the first question (e.g. their password), but it fails to retrieve the second one (e.g. the key passphrase). So the user would be forced to reenter the answer for the second question upon each reconnection. The reason for this is the passwdserver's desig (see DESIGN [1]): Each query for AuthInfo with the openPassDlg() has an secNr number associated with it. If it's smaller than the one of the one stored for the privious request, than the one from the cache will be returned automagically, if it's bigger the dialog will be prompted to the user. Each call to openPassDlg() advances s_seqNr to the last value reported by the passwdserver. So the first call will return the cached value and subsequent calls will actually display the dialog to the user (assuming authentication with the cached data failed). But in case of multi-factor auth we have to query user for several independent values. And we want to try to retrieve each one of those from the cache. So we have to get a bit hacky and manually manipulate the SlaveBase::s_seqNr value. [1]: https://mirror.git.trinitydesktop.org/gitea/TDE/tdelibs/src/branch/master/tdeio/kpasswdserver/DESIGN Signed-off-by: Alexander Golubev <fatzer2@gmail.com> (cherry picked from commit 95b18e63382c4f0013c4eb2473d04f6020a84b7a)
10 months ago
kdDebug(TDEIO_SFTP_DB) << "Trying to authenticate with public key" << endl;
// First let's do some cleanup
tdeioslave/sftp: save/restore seqNr for multi-factor auth In case the server is set up for multi-factor authentication we could be have to query several things from the user like password, a key passphrase, their mother's maiden name etc. It doesn't make a big difference during an initial connection, but it butchers the reconnection process: it can retrieve the answer of the user to the first question (e.g. their password), but it fails to retrieve the second one (e.g. the key passphrase). So the user would be forced to reenter the answer for the second question upon each reconnection. The reason for this is the passwdserver's desig (see DESIGN [1]): Each query for AuthInfo with the openPassDlg() has an secNr number associated with it. If it's smaller than the one of the one stored for the privious request, than the one from the cache will be returned automagically, if it's bigger the dialog will be prompted to the user. Each call to openPassDlg() advances s_seqNr to the last value reported by the passwdserver. So the first call will return the cached value and subsequent calls will actually display the dialog to the user (assuming authentication with the cached data failed). But in case of multi-factor auth we have to query user for several independent values. And we want to try to retrieve each one of those from the cache. So we have to get a bit hacky and manually manipulate the SlaveBase::s_seqNr value. [1]: https://mirror.git.trinitydesktop.org/gitea/TDE/tdelibs/src/branch/master/tdeio/kpasswdserver/DESIGN Signed-off-by: Alexander Golubev <fatzer2@gmail.com> (cherry picked from commit 95b18e63382c4f0013c4eb2473d04f6020a84b7a)
10 months ago
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;
tdeioslave/sftp: save/restore seqNr for multi-factor auth In case the server is set up for multi-factor authentication we could be have to query several things from the user like password, a key passphrase, their mother's maiden name etc. It doesn't make a big difference during an initial connection, but it butchers the reconnection process: it can retrieve the answer of the user to the first question (e.g. their password), but it fails to retrieve the second one (e.g. the key passphrase). So the user would be forced to reenter the answer for the second question upon each reconnection. The reason for this is the passwdserver's desig (see DESIGN [1]): Each query for AuthInfo with the openPassDlg() has an secNr number associated with it. If it's smaller than the one of the one stored for the privious request, than the one from the cache will be returned automagically, if it's bigger the dialog will be prompted to the user. Each call to openPassDlg() advances s_seqNr to the last value reported by the passwdserver. So the first call will return the cached value and subsequent calls will actually display the dialog to the user (assuming authentication with the cached data failed). But in case of multi-factor auth we have to query user for several independent values. And we want to try to retrieve each one of those from the cache. So we have to get a bit hacky and manually manipulate the SlaveBase::s_seqNr value. [1]: https://mirror.git.trinitydesktop.org/gitea/TDE/tdelibs/src/branch/master/tdeio/kpasswdserver/DESIGN Signed-off-by: Alexander Golubev <fatzer2@gmail.com> (cherry picked from commit 95b18e63382c4f0013c4eb2473d04f6020a84b7a)
10 months ago
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;
tdeioslave/sftp: save/restore seqNr for multi-factor auth In case the server is set up for multi-factor authentication we could be have to query several things from the user like password, a key passphrase, their mother's maiden name etc. It doesn't make a big difference during an initial connection, but it butchers the reconnection process: it can retrieve the answer of the user to the first question (e.g. their password), but it fails to retrieve the second one (e.g. the key passphrase). So the user would be forced to reenter the answer for the second question upon each reconnection. The reason for this is the passwdserver's desig (see DESIGN [1]): Each query for AuthInfo with the openPassDlg() has an secNr number associated with it. If it's smaller than the one of the one stored for the privious request, than the one from the cache will be returned automagically, if it's bigger the dialog will be prompted to the user. Each call to openPassDlg() advances s_seqNr to the last value reported by the passwdserver. So the first call will return the cached value and subsequent calls will actually display the dialog to the user (assuming authentication with the cached data failed). But in case of multi-factor auth we have to query user for several independent values. And we want to try to retrieve each one of those from the cache. So we have to get a bit hacky and manually manipulate the SlaveBase::s_seqNr value. [1]: https://mirror.git.trinitydesktop.org/gitea/TDE/tdelibs/src/branch/master/tdeio/kpasswdserver/DESIGN Signed-off-by: Alexander Golubev <fatzer2@gmail.com> (cherry picked from commit 95b18e63382c4f0013c4eb2473d04f6020a84b7a)
10 months ago
int rc = SSH_AUTH_ERROR;
bool retryDenied = false; // a flag to avoid infinite looping
TQString cachablePassword;
PasswordPurger cachePurger(cachablePassword);
tdeioslave/sftp: save/restore seqNr for multi-factor auth In case the server is set up for multi-factor authentication we could be have to query several things from the user like password, a key passphrase, their mother's maiden name etc. It doesn't make a big difference during an initial connection, but it butchers the reconnection process: it can retrieve the answer of the user to the first question (e.g. their password), but it fails to retrieve the second one (e.g. the key passphrase). So the user would be forced to reenter the answer for the second question upon each reconnection. The reason for this is the passwdserver's desig (see DESIGN [1]): Each query for AuthInfo with the openPassDlg() has an secNr number associated with it. If it's smaller than the one of the one stored for the privious request, than the one from the cache will be returned automagically, if it's bigger the dialog will be prompted to the user. Each call to openPassDlg() advances s_seqNr to the last value reported by the passwdserver. So the first call will return the cached value and subsequent calls will actually display the dialog to the user (assuming authentication with the cached data failed). But in case of multi-factor auth we have to query user for several independent values. And we want to try to retrieve each one of those from the cache. So we have to get a bit hacky and manually manipulate the SlaveBase::s_seqNr value. [1]: https://mirror.git.trinitydesktop.org/gitea/TDE/tdelibs/src/branch/master/tdeio/kpasswdserver/DESIGN Signed-off-by: Alexander Golubev <fatzer2@gmail.com> (cherry picked from commit 95b18e63382c4f0013c4eb2473d04f6020a84b7a)
10 months ago
// 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) {
tdeioslave/sftp: save/restore seqNr for multi-factor auth In case the server is set up for multi-factor authentication we could be have to query several things from the user like password, a key passphrase, their mother's maiden name etc. It doesn't make a big difference during an initial connection, but it butchers the reconnection process: it can retrieve the answer of the user to the first question (e.g. their password), but it fails to retrieve the second one (e.g. the key passphrase). So the user would be forced to reenter the answer for the second question upon each reconnection. The reason for this is the passwdserver's desig (see DESIGN [1]): Each query for AuthInfo with the openPassDlg() has an secNr number associated with it. If it's smaller than the one of the one stored for the privious request, than the one from the cache will be returned automagically, if it's bigger the dialog will be prompted to the user. Each call to openPassDlg() advances s_seqNr to the last value reported by the passwdserver. So the first call will return the cached value and subsequent calls will actually display the dialog to the user (assuming authentication with the cached data failed). But in case of multi-factor auth we have to query user for several independent values. And we want to try to retrieve each one of those from the cache. So we have to get a bit hacky and manually manipulate the SlaveBase::s_seqNr value. [1]: https://mirror.git.trinitydesktop.org/gitea/TDE/tdelibs/src/branch/master/tdeio/kpasswdserver/DESIGN Signed-off-by: Alexander Golubev <fatzer2@gmail.com> (cherry picked from commit 95b18e63382c4f0013c4eb2473d04f6020a84b7a)
10 months ago
// 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;
for (i = 0; i < n; ++i) {
char echo;
bool isPassword=false;
TQString answer;
TQString errMsg;
tdeioslave/sftp: save/restore seqNr for multi-factor auth In case the server is set up for multi-factor authentication we could be have to query several things from the user like password, a key passphrase, their mother's maiden name etc. It doesn't make a big difference during an initial connection, but it butchers the reconnection process: it can retrieve the answer of the user to the first question (e.g. their password), but it fails to retrieve the second one (e.g. the key passphrase). So the user would be forced to reenter the answer for the second question upon each reconnection. The reason for this is the passwdserver's desig (see DESIGN [1]): Each query for AuthInfo with the openPassDlg() has an secNr number associated with it. If it's smaller than the one of the one stored for the privious request, than the one from the cache will be returned automagically, if it's bigger the dialog will be prompted to the user. Each call to openPassDlg() advances s_seqNr to the last value reported by the passwdserver. So the first call will return the cached value and subsequent calls will actually display the dialog to the user (assuming authentication with the cached data failed). But in case of multi-factor auth we have to query user for several independent values. And we want to try to retrieve each one of those from the cache. So we have to get a bit hacky and manually manipulate the SlaveBase::s_seqNr value. [1]: https://mirror.git.trinitydesktop.org/gitea/TDE/tdelibs/src/branch/master/tdeio/kpasswdserver/DESIGN Signed-off-by: Alexander Golubev <fatzer2@gmail.com> (cherry picked from commit 95b18e63382c4f0013c4eb2473d04f6020a84b7a)
10 months ago
// 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;
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};
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 (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;
infoKbdInt.url.setUser(infoKbdInt.username);
infoKbdInt.username = TQString::null;
infoKbdInt.readOnly = false;
if (openPassDlg(infoKbdInt)) {
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.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 (!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);
}
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;
}
}
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 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) {
kdDebug(TDEIO_SFTP_DB) << "url = " << url.url() << " - err=" << err << endl;
switch (err) {
case SSH_FX_OK:
break;
case SSH_FX_NO_SUCH_FILE:
case SSH_FX_NO_SUCH_PATH:
error(TDEIO::ERR_DOES_NOT_EXIST, url.prettyURL());
break;
case SSH_FX_PERMISSION_DENIED:
error(TDEIO::ERR_ACCESS_DENIED, url.prettyURL());
break;
case SSH_FX_FILE_ALREADY_EXISTS:
error(TDEIO::ERR_FILE_ALREADY_EXIST, url.prettyURL());
break;
case SSH_FX_INVALID_HANDLE:
error(TDEIO::ERR_MALFORMED_URL, url.prettyURL());
break;
case SSH_FX_OP_UNSUPPORTED:
error(TDEIO::ERR_UNSUPPORTED_ACTION, url.prettyURL());
break;
case SSH_FX_BAD_MESSAGE:
error(TDEIO::ERR_UNKNOWN, url.prettyURL());
break;
default:
error(TDEIO::ERR_INTERNAL, url.prettyURL());
break;
}
}
bool sftpProtocol::createUDSEntry(const TQString &filename, const TQByteArray &path,
UDSEntry &entry, short int details) {
mode_t type;
mode_t access;
char *link;
Q_ASSERT(entry.count() == 0);
sftp_attributes sb = sftp_lstat(mSftp, path.data());
if (sb == NULL) {
return false;
}
UDSAtom atom;
atom.m_uds = UDS_NAME;
atom.m_str = filename;
entry.append(atom);
if (sb->type == SSH_FILEXFER_TYPE_SYMLINK) {
atom.m_uds = UDS_FILE_TYPE;
atom.m_long = S_IFREG;
entry.append(atom);
link = sftp_readlink(mSftp, path.data());
if (link == NULL) {
sftp_attributes_free(sb);
return false;
}
atom.m_uds = UDS_LINK_DEST;
atom.m_str = TQFile::decodeName(link);
entry.append(atom);
delete link;
// A symlink -> follow it only if details > 1
if (details > 1) {
sftp_attributes sb2 = sftp_stat(mSftp, path.data());
if (sb2 == NULL) {
// It is a link pointing to nowhere
type = S_IFMT - 1;
access = S_IRWXU | S_IRWXG | S_IRWXO;
atom.m_uds = UDS_FILE_TYPE;
atom.m_long = type;
entry.append(atom);
atom.m_uds = UDS_ACCESS;
atom.m_long = access;
entry.append(atom);
atom.m_uds = UDS_SIZE;
atom.m_long = 0LL;
entry.append(atom);
goto notype;
}
sftp_attributes_free(sb);
sb = sb2;
}
}
switch (sb->type) {
case SSH_FILEXFER_TYPE_REGULAR:
atom.m_uds = UDS_FILE_TYPE;
atom.m_long = S_IFREG;
entry.append(atom);
break;
case SSH_FILEXFER_TYPE_DIRECTORY:
atom.m_uds = UDS_FILE_TYPE;
atom.m_long = S_IFDIR;
entry.append(atom);
break;
case SSH_FILEXFER_TYPE_SYMLINK:
atom.m_uds = UDS_FILE_TYPE;
atom.m_long = S_IFLNK;
entry.append(atom);
break;
case SSH_FILEXFER_TYPE_SPECIAL:
case SSH_FILEXFER_TYPE_UNKNOWN:
atom.m_uds = UDS_FILE_TYPE;
atom.m_long = S_IFMT - 1;
entry.append(atom);
break;
}
access = sb->permissions & 07777;
atom.m_uds = UDS_ACCESS;
atom.m_long = access;
entry.append(atom);
atom.m_uds = UDS_SIZE;
atom.m_long = sb->size;
entry.append(atom);
notype:
if (details > 0) {
if (sb->owner) {
atom.m_uds = UDS_USER;
atom.m_str = TQString::fromUtf8(sb->owner);
entry.append(atom);
} else {
atom.m_uds = UDS_USER;
atom.m_str = TQString::number(sb->uid);
entry.append(atom);
}
if (sb->group) {
atom.m_uds = UDS_GROUP;
atom.m_str = TQString::fromUtf8(sb->group);
entry.append(atom);
} else {
atom.m_uds = UDS_GROUP;
atom.m_str = TQString::number(sb->gid);
entry.append(atom);
}
atom.m_uds = UDS_ACCESS_TIME;
atom.m_long = sb->atime;
entry.append(atom);
atom.m_uds = UDS_MODIFICATION_TIME;
atom.m_long = sb->mtime;
entry.append(atom);
atom.m_uds = UDS_MODIFICATION_TIME;
atom.m_long = sb->createtime;
entry.append(atom);
}
sftp_attributes_free(sb);
return true;
}
TQString sftpProtocol::canonicalizePath(const TQString &path) {
kdDebug(TDEIO_SFTP_DB) << "Path to canonicalize: " << path << endl;
TQString cPath;
char *sPath = NULL;
if (path.isEmpty()) {
return cPath;
}
sPath = sftp_canonicalize_path(mSftp, path.utf8().data());
if (sPath == NULL) {
kdDebug(TDEIO_SFTP_DB) << "Could not canonicalize path: " << path << endl;
return cPath;
}
cPath = TQFile::decodeName(sPath);
delete sPath;
kdDebug(TDEIO_SFTP_DB) << "Canonicalized path: " << cPath << endl;
return cPath;
}
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 TQ_WS_WIN
kdDebug(TDEIO_SFTP_DB) << "pid = " << getpid() << endl;
kdDebug(TDEIO_SFTP_DB) << "debug = " << getenv("TDEIO_SFTP_LOG_VERBOSITY") << endl;
#endif
mCallbacks = (ssh_callbacks) malloc(sizeof(struct ssh_callbacks_struct));
if (mCallbacks == NULL) {
error(TDEIO::ERR_OUT_OF_MEMORY, i18n("Could not allocate callbacks"));
return;
}
ZERO_STRUCTP(mCallbacks);
mCallbacks->userdata = this;
mCallbacks->auth_function = ::auth_callback;
if (getenv("TDEIO_SFTP_LOG_VERBOSITY")) {
mCallbacks->log_function = ::log_callback;
}
ssh_callbacks_init(mCallbacks);
}
sftpProtocol::~sftpProtocol() {
#ifndef TQ_WS_WIN
kdDebug(TDEIO_SFTP_DB) << "pid = " << getpid() << endl;
#endif
closeConnection();
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) {
kdDebug(TDEIO_SFTP_DB) << "setHost(): " << user << "@" << h << ":" << port << endl;
if (mConnected) {
closeConnection();
}
mHost = h;
if (port > 0) {
mPort = port;
} else {
struct servent *pse;
if ((pse = getservbyname("ssh", "tcp") ) == NULL) {
mPort = 22;
} else {
mPort = ntohs(pse->s_port);
}
}
kdDebug(TDEIO_SFTP_DB) << "setHost(): mPort=" << mPort << endl;
mUsername = user;
mPassword = pass;
mCachedUsername = TQString::null;
}
int sftpProtocol::initializeConnection() {
unsigned char *hash = NULL; // the server hash
char *hexa;
char *verbosity;
int rc, state;
int timeout_sec = 30, timeout_usec = 0;
mSession = ssh_new();
if (mSession == NULL) {
error(TDEIO::ERR_INTERNAL, i18n("Could not create a new SSH session."));
return SSH_ERROR;
}
kdDebug(TDEIO_SFTP_DB) << "Creating the SSH session and setting options" << endl;
// Set timeout
rc = ssh_options_set(mSession, SSH_OPTIONS_TIMEOUT, &timeout_sec);
if (rc < 0) {
kdDebug(TDEIO_SFTP_DB) << "Could not set a timeout.";
}
rc = ssh_options_set(mSession, SSH_OPTIONS_TIMEOUT_USEC, &timeout_usec);
if (rc < 0) {
kdDebug(TDEIO_SFTP_DB) << "Could not set a timeout in usec.";
}
// Don't use any compression
rc = ssh_options_set(mSession, SSH_OPTIONS_COMPRESSION_C_S, "none");
if (rc < 0) {
kdDebug(TDEIO_SFTP_DB) << "Could not set compression client <- server.";
}
rc = ssh_options_set(mSession, SSH_OPTIONS_COMPRESSION_S_C, "none");
if (rc < 0) {
kdDebug(TDEIO_SFTP_DB) << "Could not set compression server -> client.";
}
// Set host and port
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 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 SSH_ERROR;
}
}
// Set the username
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 rc;
}
}
verbosity = getenv("TDEIO_SFTP_LOG_VERBOSITY");
if (verbosity) {
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 rc;
}
}
// Read ~/.ssh/config
rc = ssh_options_parse_config(mSession, NULL);
if (rc < 0) {
error(TDEIO::ERR_INTERNAL, i18n("Could not parse the config file."));
return rc;
}
ssh_set_callbacks(mSession, mCallbacks);
kdDebug(TDEIO_SFTP_DB) << "Trying to connect to the SSH server" << endl;
/* try to connect */
rc = ssh_connect(mSession);
if (rc < 0) {
error(TDEIO::ERR_COULD_NOT_CONNECT, sshError());
return rc;
}
ExitGuard connectionCloser([this](){ closeConnection(); });
kdDebug(TDEIO_SFTP_DB) << "Getting the SSH server hash" << endl;
/* get the hash */
ssh_key serverKey;
#if LIBSSH_VERSION_INT < SSH_VERSION_INT(0, 7, 90)
rc = ssh_get_publickey(mSession, &serverKey);
#else
rc = ssh_get_server_publickey(mSession, &serverKey);
#endif
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)
rc = ssh_get_publickey_hash(serverKey, SSH_PUBLICKEY_HASH_MD5, &hash, &hlen);
#else
rc = ssh_get_publickey_hash(serverKey, SSH_PUBLICKEY_HASH_SHA256, &hash, &hlen);
#endif
if (rc<0) {
error(TDEIO::ERR_COULD_NOT_CONNECT, sshError());
return rc;
}
kdDebug(TDEIO_SFTP_DB) << "Checking if the SSH server is known" << endl;
/* check the server public key hash */
#if LIBSSH_VERSION_INT < SSH_VERSION_INT(0, 7, 90)
state = ssh_is_server_known(mSession);
#else
state = ssh_session_is_known_server(mSession);
#endif
switch (state) {
case TDEIO_SSH_KNOWN_HOSTS_OK:
break;
case TDEIO_SSH_KNOWN_HOSTS_OTHER:
delete hash;
error(TDEIO::ERR_CONNECTION_BROKEN, i18n("The host key for this server was "
"not found, but another type of key exists.\n"
"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))));
return SSH_ERROR;
case TDEIO_SSH_KNOWN_HOSTS_CHANGED:
hexa = ssh_get_hexa(hash, hlen);
delete hash;
/* TODO print known_hosts file, port? */
error(TDEIO::ERR_CONNECTION_BROKEN, i18n("The host key for the server %1 has changed.\n"
"This could either mean that DNS SPOOFING is happening or the IP "
"address for the host and its host key have changed at the same time.\n"
"The fingerprint for the key sent by the remote host is:\n %2\n"
"Please contact your system administrator.\n%3").arg(
mHost).arg(TQString::fromUtf8(hexa)).arg(TQString::fromUtf8(ssh_get_error(mSession))));
delete hexa;
return SSH_ERROR;
case TDEIO_SSH_KNOWN_HOSTS_NOT_FOUND:
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.");
msg = i18n("The authenticity of host %1 cannot be established.\n"
"The key fingerprint is: %2\n"
"Are you sure you want to continue connecting?").arg(mHost).arg(hexa);
delete hexa;
if (KMessageBox::Yes != messageBox(WarningYesNo, msg, caption)) {
error(TDEIO::ERR_USER_CANCELED, TQString());
return SSH_ERROR;
}
/* write the known_hosts file */
kdDebug(TDEIO_SFTP_DB) << "Adding server to known_hosts file." << endl;
#if LIBSSH_VERSION_INT < SSH_VERSION_INT(0, 7, 90)
if (ssh_write_knownhost(mSession) != SSH_OK) {
#else
if (ssh_session_update_known_hosts(mSession) != SSH_OK) {
#endif
error(TDEIO::ERR_USER_CANCELED, sshError());
return SSH_ERROR;
}
break;
}
case TDEIO_SSH_KNOWN_HOSTS_ERROR:
delete hash;
error(TDEIO::ERR_COULD_NOT_CONNECT, sshError());
return SSH_ERROR;
}
kdDebug(TDEIO_SFTP_DB) << "Trying to authenticate with the server" << endl;
connectionCloser.abort();
return SSH_OK;
}
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;
}
// 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
}
}
mPasswordWasPrompted = false;
PasswordPurger pwPurger{mPassword};
int rc;
connection_restart:
// Start the ssh connection.
if (initializeConnection() < 0) {
return;
}
ExitGuard connectionCloser([this](){ closeConnection(); });
// 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;
}
// 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;
tdeioslave/sftp: save/restore seqNr for multi-factor auth In case the server is set up for multi-factor authentication we could be have to query several things from the user like password, a key passphrase, their mother's maiden name etc. It doesn't make a big difference during an initial connection, but it butchers the reconnection process: it can retrieve the answer of the user to the first question (e.g. their password), but it fails to retrieve the second one (e.g. the key passphrase). So the user would be forced to reenter the answer for the second question upon each reconnection. The reason for this is the passwdserver's desig (see DESIGN [1]): Each query for AuthInfo with the openPassDlg() has an secNr number associated with it. If it's smaller than the one of the one stored for the privious request, than the one from the cache will be returned automagically, if it's bigger the dialog will be prompted to the user. Each call to openPassDlg() advances s_seqNr to the last value reported by the passwdserver. So the first call will return the cached value and subsequent calls will actually display the dialog to the user (assuming authentication with the cached data failed). But in case of multi-factor auth we have to query user for several independent values. And we want to try to retrieve each one of those from the cache. So we have to get a bit hacky and manually manipulate the SlaveBase::s_seqNr value. [1]: https://mirror.git.trinitydesktop.org/gitea/TDE/tdelibs/src/branch/master/tdeio/kpasswdserver/DESIGN Signed-off-by: Alexander Golubev <fatzer2@gmail.com> (cherry picked from commit 95b18e63382c4f0013c4eb2473d04f6020a84b7a)
10 months ago
// 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);
tdeioslave/sftp: save/restore seqNr for multi-factor auth In case the server is set up for multi-factor authentication we could be have to query several things from the user like password, a key passphrase, their mother's maiden name etc. It doesn't make a big difference during an initial connection, but it butchers the reconnection process: it can retrieve the answer of the user to the first question (e.g. their password), but it fails to retrieve the second one (e.g. the key passphrase). So the user would be forced to reenter the answer for the second question upon each reconnection. The reason for this is the passwdserver's desig (see DESIGN [1]): Each query for AuthInfo with the openPassDlg() has an secNr number associated with it. If it's smaller than the one of the one stored for the privious request, than the one from the cache will be returned automagically, if it's bigger the dialog will be prompted to the user. Each call to openPassDlg() advances s_seqNr to the last value reported by the passwdserver. So the first call will return the cached value and subsequent calls will actually display the dialog to the user (assuming authentication with the cached data failed). But in case of multi-factor auth we have to query user for several independent values. And we want to try to retrieve each one of those from the cache. So we have to get a bit hacky and manually manipulate the SlaveBase::s_seqNr value. [1]: https://mirror.git.trinitydesktop.org/gitea/TDE/tdelibs/src/branch/master/tdeio/kpasswdserver/DESIGN Signed-off-by: Alexander Golubev <fatzer2@gmail.com> (cherry picked from commit 95b18e63382c4f0013c4eb2473d04f6020a84b7a)
10 months ago
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;
}
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::make_unique<KeyboardInteractiveAuth>(/* noPasswordQuery = */true));
rv.emplace_back(std::make_unique<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_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;
goto connection_restart;
} 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 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
kdDebug(TDEIO_SFTP_DB) << "Trying to request the sftp session" << endl;
mSftp = sftp_new(mSession);
if (mSftp == NULL) {
error(TDEIO::ERR_COULD_NOT_LOGIN, i18n("Unable to request the SFTP subsystem. "
"Make sure SFTP is enabled on the server."));
return;
}
kdDebug(TDEIO_SFTP_DB) << "Trying to initialize the sftp session" << endl;
if (sftp_init(mSftp) < 0) {
error(TDEIO::ERR_COULD_NOT_LOGIN, i18n("Could not initialize the SFTP session."));
return;
}
// Login succeeded!
infoMessage(i18n("Successfully connected to %1").arg(mHost));
//setTimeoutSpecialCommand(TDEIO_SFTP_SPECIAL_TIMEOUT);
mConnected = true;
connectionCloser.abort();
connected();
return;
}
void sftpProtocol::closeConnection() {
kdDebug(TDEIO_SFTP_DB) << "closeConnection()" << endl;
sftp_free(mSftp);
mSftp = NULL;
ssh_disconnect(mSession);
mSession = NULL;
mConnected = false;
}
#if 0
void sftpProtocol::special(const TQByteArray &data) {
int rc;
kdDebug(TDEIO_SFTP_DB) << "special(): polling";
/*
* channel_poll() returns the number of bytes that may be read on the
* channel. It does so by checking the input buffer and eventually the
* network socket for data to read. If the input buffer is not empty, it
* will not probe the network (and such not read packets nor reply to
* keepalives).
*
* As channel_poll can act on two specific buffers (a channel has two
* different stream: stdio and stderr), polling for data on the stderr
* stream has more chance of not being in the problematic case (data left
* in the buffer). Checking the return value (for >0) would be a good idea
* to debug the problem.
*/
rc = channel_poll(mSftp->channel, 0);
if (rc > 0) {
rc = channel_poll(mSftp->channel, 1);
}
if (rc < 0) {
kdDebug(TDEIO_SFTP_DB) << "channel_poll failed: " << ssh_get_error(mSession) << endl;
}
setTimeoutSpecialCommand(TDEIO_SFTP_SPECIAL_TIMEOUT);
}
#endif
void sftpProtocol::statMime(const KURL &url) {
kdDebug(TDEIO_SFTP_DB) << "stat: " << url.url() << endl;
openConnection();
if (!mConnected) {
error(TDEIO::ERR_CONNECTION_BROKEN, url.prettyURL());
return;
}
const TQString path = url.path();
const TQByteArray path_c = path.utf8();
sftp_attributes sb = sftp_lstat(mSftp, path_c.data());
if (sb == NULL) {
reportError(url, sftp_get_error(mSftp));
return;
}
switch (sb->type) {
case SSH_FILEXFER_TYPE_DIRECTORY:
sftp_attributes_free(sb);
emit mimeType("inode/directory");
return;
case SSH_FILEXFER_TYPE_SPECIAL:
case SSH_FILEXFER_TYPE_UNKNOWN:
error(TDEIO::ERR_CANNOT_OPEN_FOR_READING, url.prettyURL());
sftp_attributes_free(sb);
return;
case SSH_FILEXFER_TYPE_SYMLINK:
case SSH_FILEXFER_TYPE_REGULAR:
break;
}
size_t fileSize = sb->size;
sftp_attributes_free(sb);
int flags = 0;
flags = O_RDONLY;
mOpenFile = sftp_open(mSftp, path_c.data(), flags, 0);
if (mOpenFile == NULL) {
error(TDEIO::ERR_CANNOT_OPEN_FOR_READING, path);
return;
}
// Determine the mimetype of the file to be retrieved, and emit it.
// This is mandatory in all slaves (for KRun/BrowserRun to work).
// If we're not opening the file ReadOnly or ReadWrite, don't attempt to
// read the file and send the mimetype.
size_t bytesRequested = 1024;
ssize_t bytesRead = 0;
TQByteArray buffer(bytesRequested);
bytesRead = sftp_read(mOpenFile, buffer.data(), bytesRequested);
if (bytesRead < 0) {
error(TDEIO::ERR_COULD_NOT_READ, mOpenUrl.prettyURL());
closeFile();
return;
} else {
TQByteArray fileData;
fileData.setRawData(buffer.data(), bytesRead);
KMimeMagicResult *p_mimeType = KMimeMagic::self()->findBufferFileType(fileData, mOpenUrl.fileName());
emit mimeType(p_mimeType->mimeType());
}
sftp_close(mOpenFile);
mOpenFile = NULL;
}
#if 0
void sftpProtocol::read(TDEIO::filesize_t bytes) {
kdDebug(TDEIO_SFTP_DB) << "read, offset = " << openOffset << ", bytes = " << bytes;
Q_ASSERT(mOpenFile != NULL);
TQVarLengthArray<char> buffer(bytes);
ssize_t bytesRead = sftp_read(mOpenFile, buffer.data(), bytes);
Q_ASSERT(bytesRead <= static_cast<ssize_t>(bytes));
if (bytesRead < 0) {
kdDebug(TDEIO_SFTP_DB) << "Could not read " << mOpenUrl;
error(TDEIO::ERR_COULD_NOT_READ, mOpenUrl.prettyURL());
close();
return;
}
TQByteArray fileData = TQByteArray::fromRawData(buffer.data(), bytesRead);
data(fileData);
}
void sftpProtocol::write(const TQByteArray &data) {
kdDebug(TDEIO_SFTP_DB) << "write, offset = " << openOffset << ", bytes = " << data.size();
Q_ASSERT(mOpenFile != NULL);
ssize_t bytesWritten = sftp_write(mOpenFile, data.data(), data.size());
if (bytesWritten < 0) {
kdDebug(TDEIO_SFTP_DB) << "Could not write to " << mOpenUrl;
error(TDEIO::ERR_COULD_NOT_WRITE, mOpenUrl.prettyURL());
close();
return;
}
written(bytesWritten);
}
void sftpProtocol::seek(TDEIO::filesize_t offset) {
kdDebug(TDEIO_SFTP_DB) << "seek, offset = " << offset;
Q_ASSERT(mOpenFile != NULL);
if (sftp_seek64(mOpenFile, static_cast<uint64_t>(offset)) < 0) {
error(TDEIO::ERR_COULD_NOT_SEEK, mOpenUrl.path());
close();
}
position(sftp_tell64(mOpenFile));
}
#endif
void sftpProtocol::closeFile() {
if (mOpenFile) {
sftp_close(mOpenFile);
mOpenFile = NULL;
finished();
}
}
void sftpProtocol::get(const KURL& url) {
kdDebug(TDEIO_SFTP_DB) << "get(): " << url.url() << endl;
openConnection();
if (!mConnected) {
return;
}
TQByteArray path = url.path().utf8();
char buf[MAX_XFER_BUF_SIZE] = {0};
sftp_file file = NULL;
ssize_t bytesread = 0;
// time_t curtime = 0;
time_t lasttime = 0;
time_t starttime = 0;
ssize_t totalbytesread = 0;
sftp_attributes sb = sftp_lstat(mSftp, path.data());
if (sb == NULL) {
reportError(url, sftp_get_error(mSftp));
return;
}
switch (sb->type) {
case SSH_FILEXFER_TYPE_DIRECTORY:
error(TDEIO::ERR_IS_DIRECTORY, url.prettyURL());
sftp_attributes_free(sb);
return;
case SSH_FILEXFER_TYPE_SPECIAL:
case SSH_FILEXFER_TYPE_UNKNOWN:
error(TDEIO::ERR_CANNOT_OPEN_FOR_READING, url.prettyURL());
sftp_attributes_free(sb);
return;
case SSH_FILEXFER_TYPE_SYMLINK:
case SSH_FILEXFER_TYPE_REGULAR:
break;
}
// Open file
file = sftp_open(mSftp, path.data(), O_RDONLY, 0);
if (file == NULL) {
error( TDEIO::ERR_CANNOT_OPEN_FOR_READING, url.prettyURL());
sftp_attributes_free(sb);
return;
}
// Determine the mimetype of the file to be retrieved, and emit it.
// This is mandatory in all slaves (for KRun/BrowserRun to work)
// In real "remote" slaves, this is usually done using findByNameAndContent
// after receiving some data. But we don't know how much data the mimemagic rules
// need, so for local files, better use findByUrl with localUrl=true.
KMimeType::Ptr mt = KMimeType::findByURL( url, sb->permissions, false /* remote URL */ );
emit mimeType( mt->name() ); // FIXME test me
kdDebug(TDEIO_SFTP_DB) << "Total size: " << TQString::number(sb->size) << endl;
// Set the total size
totalSize(sb->size);
const TQString resumeOffset = metaData(TQString("resume"));
if (!resumeOffset.isEmpty()) {
bool ok;
ssize_t offset = resumeOffset.toLong(&ok);
if (ok && (offset > 0) && ((unsigned long long) offset < sb->size))
{
if (sftp_seek64(file, offset) == 0) {
canResume();
totalbytesread = offset;
kdDebug(TDEIO_SFTP_DB) << "Resume offset: " << TQString::number(offset) << endl;
}
}
}
if (file != NULL) {
bool isFirstPacket = true;
lasttime = starttime = time(NULL);
for (;;) {
bytesread = sftp_read(file, buf, MAX_XFER_BUF_SIZE);
kdDebug(TDEIO_SFTP_DB) << "bytesread=" << TQString::number(bytesread) << endl;
if (bytesread == 0) {
// All done reading
break;
} else if (bytesread < 0) {
kdDebug(TDEIO_SFTP_DB) << "Failed to read";
error(TDEIO::ERR_COULD_NOT_READ, url.prettyURL());
sftp_attributes_free(sb);
return;
}
TQByteArray filedata;
filedata.setRawData(buf, bytesread);
if (isFirstPacket) {
KMimeMagicResult *p_mimeType = KMimeMagic::self()->findBufferFileType(filedata, mOpenUrl.fileName());
mimeType(p_mimeType->mimeType());
kdDebug(TDEIO_SFTP_DB) << "mimetype=" << p_mimeType->mimeType() << endl;
isFirstPacket = false;
}
data(filedata);
filedata.resetRawData(buf, bytesread);
// increment total bytes read
totalbytesread += bytesread;
processedSize(totalbytesread);
}
kdDebug(TDEIO_SFTP_DB) << "size processed=" << totalbytesread << endl;
sftp_close(file);
//data(TQByteArray());
processedSize((sb->size));
}
sftp_attributes_free(sb);
finished();
}
void sftpProtocol::put(const KURL& url, int permissions, bool overwrite, bool resume) {
kdDebug(TDEIO_SFTP_DB) << "put(): " << url.url()
<< " , permissions = " << TQString::number(permissions)
<< ", overwrite = " << overwrite
<< ", resume = " << resume << endl;
openConnection();
if (!mConnected) {
return;
}
const TQString dest_orig = url.path();
const TQByteArray dest_orig_c = dest_orig.utf8();
const TQString dest_part = dest_orig + ".part";
const TQByteArray dest_part_c = dest_part.utf8();
uid_t owner = 0;
gid_t group = 0;
sftp_attributes sb = sftp_lstat(mSftp, dest_orig_c.data());
const bool bOrigExists = (sb != NULL);
bool bPartExists = false;
const bool bMarkPartial = config()->readEntry("MarkPartial", "true") == "true";
// Don't change permissions of the original file
if (bOrigExists) {
permissions = sb->permissions;
owner = sb->uid;
group = sb->gid;
}
if (bMarkPartial) {
sftp_attributes sbPart = sftp_lstat(mSftp, dest_part_c.data());
bPartExists = (sbPart != NULL);
if (bPartExists && !resume && !overwrite &&
sbPart->size > 0 && sbPart->type == SSH_FILEXFER_TYPE_REGULAR) {
kdDebug(TDEIO_SFTP_DB) << "put : calling canResume with "
<< TQString::number(sbPart->size) << endl;
// Maybe we can use this partial file for resuming
// Tell about the size we have, and the app will tell us
// if it's ok to resume or not.
if (canResume(sbPart->size)) {
resume = true;
}
kdDebug(TDEIO_SFTP_DB) << "put got answer " << resume << endl;
delete sbPart;
}
}
if (bOrigExists && !(overwrite) && !(resume)) {
if (sb->type == SSH_FILEXFER_TYPE_DIRECTORY) {
error(TDEIO::ERR_DIR_ALREADY_EXIST, dest_orig);
} else {
error(TDEIO::ERR_FILE_ALREADY_EXIST, dest_orig);
}
sftp_attributes_free(sb);
return;
}
int result;
TQByteArray dest;
sftp_file file = NULL;
// Loop until we got 0 (end of data)
do {
TQByteArray buffer;
dataReq(); // Request for data
result = readData(buffer);
if (result >= 0 && buffer.size()) {
kdDebug(TDEIO_SFTP_DB) << TQString("Got %1 bytes of data").arg(buffer.size()) << endl;
if (dest.isEmpty()) {
if (bMarkPartial) {
kdDebug(TDEIO_SFTP_DB) << "Appending .part extension to " << dest_orig << endl;
dest = dest_part_c;
if (bPartExists && !(resume)) {
kdDebug(TDEIO_SFTP_DB) << "Deleting partial file " << dest_part << endl;
sftp_unlink(mSftp, dest_part_c.data());
// Catch errors when we try to open the file.
}
} else {
dest = dest_orig_c;
if (bOrigExists && !(resume)) {
kdDebug(TDEIO_SFTP_DB) << "Deleting destination file " << dest_orig << endl;
sftp_unlink(mSftp, dest_orig_c.data());
// Catch errors when we try to open the file.
}
} // bMarkPartial
if ((resume)) {
sftp_attributes fstat;
kdDebug(TDEIO_SFTP_DB) << "Trying to append: " << dest.data() << endl;
file = sftp_open(mSftp, dest.data(), O_RDWR, 0); // append if resuming
if (file) {
fstat = sftp_fstat(file);
if (fstat) {
sftp_seek64(file, fstat->size); // Seek to end TODO
sftp_attributes_free(fstat);
}
}
} else {
mode_t initialMode;
if (permissions != -1) {
initialMode = permissions | S_IWUSR | S_IRUSR;
} else {
initialMode = 0644;
}
kdDebug(TDEIO_SFTP_DB) << "Trying to open: " << dest.data() << ", mode=" << TQString::number(initialMode) << endl;
file = sftp_open(mSftp, dest.data(), O_CREAT | O_TRUNC | O_WRONLY, initialMode);
} // resume
if (file == NULL) {
kdDebug(TDEIO_SFTP_DB) << "COULD NOT WRITE " << dest.data()
<< " permissions=" << permissions
<< " error=" << ssh_get_error(mSession) << endl;
if (sftp_get_error(mSftp) == SSH_FX_PERMISSION_DENIED) {
error(TDEIO::ERR_WRITE_ACCESS_DENIED, TQString::fromUtf8(dest));
} else {
error(TDEIO::ERR_CANNOT_OPEN_FOR_WRITING, TQString::fromUtf8(dest));
}
sftp_attributes_free(sb);
finished();
return;
} // file
} // dest.isEmpty
ssize_t bytesWritten = sftp_write(file, buffer.data(), buffer.size());
kdDebug(TDEIO_SFTP_DB) << TQString("Written %1 bytes").arg(bytesWritten) << endl;
if (bytesWritten < 0) {
error(TDEIO::ERR_COULD_NOT_WRITE, dest_orig);
result = -1;
}
} // result
} while (result > 0);
sftp_attributes_free(sb);
// An error occurred deal with it.
if (result < 0) {
kdDebug(TDEIO_SFTP_DB) << "Error during 'put'. Aborting." << endl;
if (file != NULL) {
sftp_close(file);
sftp_attributes attr = sftp_stat(mSftp, dest.data());
if (bMarkPartial && attr != NULL) {
size_t size = config()->readLongNumEntry("MinimumKeepSize", DEFAULT_MINIMUM_KEEP_SIZE);
if (attr->size < size) {
sftp_unlink(mSftp, dest.data());
}
}
delete attr;
sftp_attributes_free(attr);
}
//::exit(255);
finished();
return;
}
if (file == NULL) { // we got nothing to write out, so we never opened the file
finished();
return;
}
if (sftp_close(file) < 0) {
kdWarning(TDEIO_SFTP_DB) << "Error when closing file descriptor" << endl;
error(TDEIO::ERR_COULD_NOT_WRITE, dest_orig);
return;
}
// after full download rename the file back to original name
if (bMarkPartial) {
// If the original URL is a symlink and we were asked to overwrite it,
// remove the symlink first. This ensures that we do not overwrite the
// current source if the symlink points to it.
if ((overwrite)) {
sftp_unlink(mSftp, dest_orig_c.data());
}
if (sftp_rename(mSftp, dest.data(), dest_orig_c.data()) < 0) {
kdWarning(TDEIO_SFTP_DB) << " Couldn't rename " << dest.data() << " to " << dest_orig << endl;
error(TDEIO::ERR_CANNOT_RENAME_PARTIAL, dest_orig);
return;
}
}
// set final permissions
if (permissions != -1 && !(resume)) {
kdDebug(TDEIO_SFTP_DB) << "Trying to set final permissions of " << dest_orig << " to " << TQString::number(permissions) << endl;
if (sftp_chmod(mSftp, dest_orig_c.data(), permissions) < 0) {
warning(i18n( "Could not change permissions for\n%1").arg(dest_orig));
}
}
// set original owner and group
if (bOrigExists) {
kdDebug(TDEIO_SFTP_DB) << "Trying to restore original owner and group of " << dest_orig << endl;
if (sftp_chown(mSftp, dest_orig_c.data(), owner, group) < 0) {
// warning(i18n( "Could not change owner and group for\n%1", dest_orig));
}
}
// set modification time
#if 0
const TQString mtimeStr = metaData("modified");
if (!mtimeStr.isEmpty()) {
TQDateTime dt = TQDateTime::fromString(mtimeStr, TQt::ISODate);
if (dt.isValid()) {
struct timeval times[2];
sftp_attributes attr = sftp_lstat(mSftp, dest_orig_c.data());
if (attr != NULL) {
times[0].tv_sec = attr->atime; //// access time, unchanged
times[1].tv_sec = dt.toTime_t(); // modification time
times[0].tv_usec = times[1].tv_usec = 0;
sftp_utimes(mSftp, dest_orig_c.data(), times);
sftp_attributes_free(attr);
}
}
}
#endif
// We have done our job => finish
finished();
}
void sftpProtocol::copy(const KURL &src, const KURL &dest, int permissions, bool overwrite)
{
kdDebug(TDEIO_SFTP_DB) << src.url() << " -> " << dest.url() << " , permissions = " << TQString::number(permissions)
<< ", overwrite = " << overwrite << endl;
error(TDEIO::ERR_UNSUPPORTED_ACTION, TQString());
}
void sftpProtocol::stat(const KURL& url) {
kdDebug(TDEIO_SFTP_DB) << url.url() << endl;
openConnection();
if (!mConnected) {
return;
}
if (! url.hasPath() || TQDir::isRelativePath(url.path()) ||
url.path().contains("/./") || url.path().contains("/../")) {
TQString cPath;
if (url.hasPath()) {
cPath = canonicalizePath(url.path());
} else {
cPath = canonicalizePath(TQString("."));
}
if (cPath.isEmpty()) {
error(TDEIO::ERR_MALFORMED_URL, url.prettyURL());
return;
}
KURL redir(url);
redir.setPath(cPath);
redirection(redir);
kdDebug(TDEIO_SFTP_DB) << "redirecting to " << redir.url() << endl;
finished();
return;
}
TQByteArray path = url.path().utf8();
const TQString sDetails = metaData(TQString("details"));
const int details = sDetails.isEmpty() ? 2 : sDetails.toInt();
UDSEntry entry;
entry.clear();
if (!createUDSEntry(url.fileName(), path, entry, details)) {
error(TDEIO::ERR_DOES_NOT_EXIST, url.prettyURL());
return;
}
statEntry(entry);
finished();
}
void sftpProtocol::mimetype(const KURL& url){
kdDebug(TDEIO_SFTP_DB) << url.url() << endl;
openConnection();
if (!mConnected) {
return;
}
// stat() feeds the mimetype
statMime(url);
closeFile();
finished();
}
void sftpProtocol::listDir(const KURL& url) {
kdDebug(TDEIO_SFTP_DB) << "list directory: " << url.url() << endl;
openConnection();
if (!mConnected) {
return;
}
if (! url.hasPath() || TQDir::isRelativePath(url.path()) ||
url.path().contains("/./") || url.path().contains("/../")) {
TQString cPath;
if (url.hasPath()) {
cPath = canonicalizePath(url.path());
} else {
cPath = canonicalizePath(TQString("."));
}
if (cPath.isEmpty()) {
error(TDEIO::ERR_MALFORMED_URL, url.prettyURL());
return;
}
KURL redir(url);
redir.setPath(cPath);
redirection(redir);
kdDebug(TDEIO_SFTP_DB) << "redirecting to " << redir.url() << endl;
finished();
return;
}
TQByteArray path = url.path().utf8();
sftp_dir dp = sftp_opendir(mSftp, path.data());
if (dp == NULL) {
reportError(url, sftp_get_error(mSftp));
return;
}
sftp_attributes dirent = NULL;
const TQString sDetails = metaData(TQString("details"));
const int details = sDetails.isEmpty() ? 2 : sDetails.toInt();
TQValueList<TQByteArray> entryNames;
UDSEntry entry;
kdDebug(TDEIO_SFTP_DB) << "readdir: " << path.data() << ", details: " << TQString::number(details) << endl;
UDSAtom atom;
for (;;) {
mode_t access;
mode_t type;
char *link;
dirent = sftp_readdir(mSftp, dp);
if (dirent == NULL) {
break;
}
entry.clear();
atom.m_uds = UDS_NAME;
atom.m_str = TQFile::decodeName(dirent->name);
entry.append(atom);
if (dirent->type == SSH_FILEXFER_TYPE_SYMLINK) {
TQCString file = (TQString::fromUtf8(path) + "/" + TQFile::decodeName(dirent->name)).utf8().data();
atom.m_uds = UDS_FILE_TYPE;
atom.m_long = S_IFREG;
entry.append(atom);
link = sftp_readlink(mSftp, file.data());
if (link == NULL) {
sftp_attributes_free(dirent);
error(TDEIO::ERR_INTERNAL, i18n("Could not read link: %1").arg(TQString::fromUtf8(file)));
return;
}
atom.m_uds = UDS_LINK_DEST;
atom.m_str = TQFile::decodeName(link);
entry.append(atom);
delete link;
// A symlink -> follow it only if details > 1
if (details > 1) {
sftp_attributes sb = sftp_stat(mSftp, file.data());
if (sb == NULL) {
// It is a link pointing to nowhere
type = S_IFMT - 1;
access = S_IRWXU | S_IRWXG | S_IRWXO;
atom.m_uds = UDS_FILE_TYPE;
atom.m_long = type;
entry.append(atom);
atom.m_uds = UDS_ACCESS;
atom.m_long = access;
entry.append(atom);
atom.m_uds = UDS_SIZE;
atom.m_long = 0;
entry.append(atom);
goto notype;
}
sftp_attributes_free(dirent);
dirent = sb;
}
}
switch (dirent->type) {
case SSH_FILEXFER_TYPE_REGULAR:
atom.m_uds = UDS_FILE_TYPE;
atom.m_long = S_IFREG;
entry.append(atom);
break;
case SSH_FILEXFER_TYPE_DIRECTORY:
atom.m_uds = UDS_FILE_TYPE;
atom.m_long = S_IFDIR;
entry.append(atom);
break;
case SSH_FILEXFER_TYPE_SYMLINK:
atom.m_uds = UDS_FILE_TYPE;
atom.m_long = S_IFLNK;
entry.append(atom);
break;
case SSH_FILEXFER_TYPE_SPECIAL:
case SSH_FILEXFER_TYPE_UNKNOWN:
break;
}
access = dirent->permissions & 07777;
atom.m_uds = UDS_ACCESS;
atom.m_long = access;
entry.append(atom);
atom.m_uds = UDS_SIZE;
atom.m_long = dirent->size;
entry.append(atom);
notype:
if (details > 0) {
atom.m_uds = UDS_USER;
if (dirent->owner) {
atom.m_str = TQString::fromUtf8(dirent->owner);
} else {
atom.m_str = TQString::number(dirent->uid);
}
entry.append(atom);
atom.m_uds = UDS_GROUP;
if (dirent->group) {
atom.m_str = TQString::fromUtf8(dirent->group);
} else {
atom.m_str = TQString::number(dirent->gid);
}
entry.append(atom);
atom.m_uds = UDS_ACCESS_TIME;
atom.m_long = dirent->atime;
entry.append(atom);
atom.m_uds = UDS_MODIFICATION_TIME;
atom.m_long = dirent->mtime;
entry.append(atom);
atom.m_uds = UDS_MODIFICATION_TIME;
atom.m_long = dirent->createtime;
entry.append(atom);
}
sftp_attributes_free(dirent);
listEntry(entry, false);
} // for ever
sftp_closedir(dp);
listEntry(entry, true); // ready
finished();
}
void sftpProtocol::mkdir(const KURL &url, int permissions) {
kdDebug(TDEIO_SFTP_DB) << "create directory: " << url.url() << endl;
openConnection();
if (!mConnected) {
return;
}
if (url.path().isEmpty()) {
error(TDEIO::ERR_MALFORMED_URL, url.prettyURL());
return;
}
const TQString path = url.path();
const TQByteArray path_c = path.utf8();
// Remove existing file or symlink, if requested.
if (metaData(TQString("overwrite")) == TQString("true")) {
kdDebug(TDEIO_SFTP_DB) << "overwrite set, remove existing file or symlink: " << url.url() << endl;
sftp_unlink(mSftp, path_c.data());
}
kdDebug(TDEIO_SFTP_DB) << "Trying to create directory: " << path << endl;
sftp_attributes sb = sftp_lstat(mSftp, path_c.data());
if (sb == NULL) {
if (sftp_mkdir(mSftp, path_c.data(), 0777) < 0) {
reportError(url, sftp_get_error(mSftp));
sftp_attributes_free(sb);
return;
} else {
kdDebug(TDEIO_SFTP_DB) << "Successfully created directory: " << url.url() << endl;
if (permissions != -1) {
chmod(url, permissions);
} else {
finished();
}
sftp_attributes_free(sb);
return;
}
}
if (sb->type == SSH_FILEXFER_TYPE_DIRECTORY) {
error(TDEIO::ERR_DIR_ALREADY_EXIST, path);
} else {
error(TDEIO::ERR_FILE_ALREADY_EXIST, path);
}
sftp_attributes_free(sb);
return;
}
void sftpProtocol::rename(const KURL& src, const KURL& dest, bool overwrite) {
kdDebug(TDEIO_SFTP_DB) << "rename " << src.url() << " to " << dest.url() << endl;
openConnection();
if (!mConnected) {
return;
}
TQByteArray qsrc = src.path().utf8();
TQByteArray qdest = dest.path().utf8();
sftp_attributes sb = sftp_lstat(mSftp, qdest.data());
if (sb != NULL) {
if (!overwrite) {
if (sb->type == SSH_FILEXFER_TYPE_DIRECTORY) {
error(TDEIO::ERR_DIR_ALREADY_EXIST, dest.url());
} else {
error(TDEIO::ERR_FILE_ALREADY_EXIST, dest.url());
}
sftp_attributes_free(sb);
return;
}
del(dest, sb->type == SSH_FILEXFER_TYPE_DIRECTORY ? true : false);
}
sftp_attributes_free(sb);
if (sftp_rename(mSftp, qsrc.data(), qdest.data()) < 0) {
reportError(dest, sftp_get_error(mSftp));
return;
}
finished();
}
void sftpProtocol::symlink(const TQString& target, const KURL& dest, bool overwrite) {
kdDebug(TDEIO_SFTP_DB) << "link " << target << "->" << dest.url()
<< ", overwrite = " << overwrite << endl;
openConnection();
if (!mConnected) {
return;
}
TQByteArray t = target.utf8();
TQByteArray d = dest.path().utf8();
bool failed = false;
if (sftp_symlink(mSftp, t.data(), d.data()) < 0) {
if (overwrite) {
sftp_attributes sb = sftp_lstat(mSftp, d.data());
if (sb == NULL) {
failed = true;
} else {
if (sftp_unlink(mSftp, d.data()) < 0) {
failed = true;
} else {
if (sftp_symlink(mSftp, t.data(), d.data()) < 0) {
failed = true;
}
}
}
sftp_attributes_free(sb);
}
}
if (failed) {
reportError(dest, sftp_get_error(mSftp));
return;
}
finished();
}
void sftpProtocol::chmod(const KURL& url, int permissions) {
kdDebug(TDEIO_SFTP_DB) << "change permission of " << url.url() << " to " << TQString::number(permissions) << endl;
openConnection();
if (!mConnected) {
return;
}
TQByteArray path = url.path().utf8();
if (sftp_chmod(mSftp, path.data(), permissions) < 0) {
reportError(url, sftp_get_error(mSftp));
return;
}
finished();
}
void sftpProtocol::del(const KURL &url, bool isfile){
kdDebug(TDEIO_SFTP_DB) << "deleting " << (isfile ? "file: " : "directory: ") << url.url() << endl;
openConnection();
if (!mConnected) {
return;
}
TQByteArray path = url.path().utf8();
if (isfile) {
if (sftp_unlink(mSftp, path.data()) < 0) {
reportError(url, sftp_get_error(mSftp));
return;
}
} else {
if (sftp_rmdir(mSftp, path.data()) < 0) {
reportError(url, sftp_get_error(mSftp));
return;
}
}
finished();
}
void sftpProtocol::slave_status() {
kdDebug(TDEIO_SFTP_DB) << "connected to " << mHost << "?: " << mConnected << endl;
slaveStatus((mConnected ? mHost : TQString()), mConnected);
}