SFTP: Various fixes and improvements

For details see PR #279.

Portions of code borrowed from KDE5 SFTP ioslave:

Source:  https://invent.kde.org/network/kio-extras/-/blob/master/sftp/kio_sftp.cpp
Licence: LGPLv2 or later

Signed-off-by: Mavridis Philippe <mavridisf@gmail.com>
(cherry picked from commit 7f277bc5e1)
r14.0.x
Mavridis Philippe 2 years ago
parent 6b64de55fb
commit b7c36c8ea3
No known key found for this signature in database
GPG Key ID: F8D2D7E2F989A494

@ -89,11 +89,19 @@ endif( )
# sys/time.h (tdeioslave/sftp, ksmserver, ksplashml) # sys/time.h (tdeioslave/sftp, ksmserver, ksplashml)
if( BUILD_KSMSERVER OR BUILD_KSPLASHML OR BUILD_TDEIOSLAVES) if( BUILD_KSMSERVER OR BUILD_KSPLASHML OR BUILD_TDEIOSLAVES )
check_include_file( sys/time.h HAVE_SYS_TIME_H ) check_include_file( sys/time.h HAVE_SYS_TIME_H )
check_include_files( "sys/time.h;time.h" TIME_WITH_SYS_TIME ) check_include_files( "sys/time.h;time.h" TIME_WITH_SYS_TIME )
endif( ) endif( )
# libssh (tdeioslave/sftp)
if( BUILD_TDEIOSLAVES )
pkg_search_module( LIBSSH libssh )
if( NOT LIBSSH_FOUND )
tde_message_fatal( "LibSSH is required, but was not found on your system" )
endif( )
endif( )
# pam # pam
if( WITH_PAM AND (BUILD_KCHECKPASS OR BUILD_TDM) ) if( WITH_PAM AND (BUILD_KCHECKPASS OR BUILD_TDM) )

@ -0,0 +1,3 @@
##### create translation templates ##############
tde_l10n_create_template( "tdeio_sftp" )

@ -23,7 +23,7 @@ link_directories(
tde_create_translated_desktop( tde_create_translated_desktop(
SOURCE sftp.protocol SOURCE sftp.protocol
DESTINATION ${SERVICES_INSTALL_DIR} DESTINATION ${SERVICES_INSTALL_DIR}
#PO_DIR tdeioslave-desktops PO_DIR tdeioslave-desktops
) )
@ -33,6 +33,6 @@ set( target tdeio_sftp )
tde_add_kpart( ${target} AUTOMOC tde_add_kpart( ${target} AUTOMOC
SOURCES tdeio_sftp.cpp SOURCES tdeio_sftp.cpp
LINK tdeio-shared LINK tdeio-shared ssh
DESTINATION ${PLUGIN_INSTALL_DIR} DESTINATION ${PLUGIN_INSTALL_DIR}
) )

@ -12,7 +12,7 @@ makedir=true
deleting=true deleting=true
moving=true moving=true
Icon=ftp Icon=ftp
Description=A new tdeioslave for sftp Description=A tdeioslave for sftp
X-DocPath=tdeioslave/sftp/index.html X-DocPath=tdeioslave/sftp/index.html
Icon=ftp Icon=ftp
Class=:internet Class=:internet

@ -6,6 +6,8 @@
* Copyright (c) 2022 Mavridis Philippe <mavridisf@gmail.com> * Copyright (c) 2022 Mavridis Philippe <mavridisf@gmail.com>
* Trinity port * Trinity port
* *
* Portions Copyright (c) 2020-2021 Harald Sitter <sitter@kde.org>
*
* This library is free software; you can redistribute it and/or * This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public * modify it under the terms of the GNU Library General Public
* License (LGPL) as published by the Free Software Foundation; * License (LGPL) as published by the Free Software Foundation;
@ -66,9 +68,9 @@
using namespace TDEIO; using namespace TDEIO;
extern "C" extern "C"
{ {
int kdemain( int argc, char **argv ) int KDE_EXPORT kdemain( int argc, char **argv )
{ {
TDEInstance instance( "tdeio_sftp" ); TDEInstance instance( "tdeio_sftp" );
kdDebug(TDEIO_SFTP_DB) << "*** Starting tdeio_sftp " << endl; kdDebug(TDEIO_SFTP_DB) << "*** Starting tdeio_sftp " << endl;
@ -92,7 +94,8 @@ extern "C"
// The callback function for libssh // The callback function for libssh
int auth_callback(const char *prompt, char *buf, size_t len, int auth_callback(const char *prompt, char *buf, size_t len,
int echo, int verify, void *userdata) { int echo, int verify, void *userdata)
{
if (userdata == NULL) { if (userdata == NULL) {
return -1; return -1;
} }
@ -117,39 +120,48 @@ void log_callback(ssh_session session, int priority, const char *message,
slave->log_callback(session, priority, message, userdata); slave->log_callback(session, priority, message, userdata);
} }
// Public key authentication
int sftpProtocol::auth_callback(const char *prompt, char *buf, size_t len, int sftpProtocol::auth_callback(const char *prompt, char *buf, size_t len,
int echo, int verify, void *userdata) { int echo, int verify, void *userdata)
TQString i_prompt = TQString::fromUtf8(prompt); {
// unused variables // unused variables
(void) echo; (void) echo;
(void) verify; (void) verify;
(void) userdata; (void) userdata;
kdDebug(TDEIO_SFTP_DB) << "Entering authentication callback, prompt=" << i_prompt << endl; kdDebug(TDEIO_SFTP_DB) << "Entering public key authentication callback" << endl;
TDEIO::AuthInfo info; if(!pubKeyInfo)
{
pubKeyInfo = new TDEIO::AuthInfo;
}
else
{
// TODO: inform user about incorrect password
}
info.url.setProtocol("sftp"); pubKeyInfo->url.setProtocol("sftp");
info.url.setHost(mHost); pubKeyInfo->url.setHost(mHost);
info.url.setPort(mPort); pubKeyInfo->url.setPort(mPort);
info.url.setUser(mUsername); pubKeyInfo->url.setUser(mUsername);
info.comment = "sftp://" + mUsername + "@" + mHost; pubKeyInfo->caption = i18n("SFTP Login");
info.username = i_prompt; pubKeyInfo->comment = "sftp://" + mUsername + "@" + mHost;
info.readOnly = true; pubKeyInfo->username = mUsername;
info.prompt = i_prompt; pubKeyInfo->readOnly = false;
info.keepPassword = false; // don't save passwords for public key, pubKeyInfo->prompt = TQString::fromUtf8(prompt);
// that's the task of ssh-agent. pubKeyInfo->keepPassword = false; // don't save passwords for public key,
// that's the task of ssh-agent.
if (!openPassDlg(info)) { if (!openPassDlg(*pubKeyInfo)) {
kdDebug(TDEIO_SFTP_DB) << "Password dialog failed" << endl; kdDebug(TDEIO_SFTP_DB) << "User canceled entry of public key password." << endl;
return -1; return -1;
} }
strncpy(buf, info.password.utf8().data(), len - 1); strncpy(buf, pubKeyInfo->password.utf8().data(), len - 1);
info.password.fill('x'); pubKeyInfo->password.fill('x');
pubKeyInfo->password = "";
return 0; return 0;
} }
@ -224,7 +236,7 @@ int sftpProtocol::authenticateKeyboardInteractive(AuthInfo &info) {
} }
break; break;
} else { } else {
if (prompt.lower() == "password") { if (prompt.lower().startsWith("password")) {
answer = mPassword.utf8().data(); answer = mPassword.utf8().data();
} else { } else {
info.readOnly = true; // set username readonly info.readOnly = true; // set username readonly
@ -286,7 +298,7 @@ bool sftpProtocol::createUDSEntry(const TQString &filename, const TQByteArray &p
mode_t access; mode_t access;
char *link; char *link;
ASSERT(entry.count() == 0); Q_ASSERT(entry.count() == 0);
sftp_attributes sb = sftp_lstat(mSftp, path.data()); sftp_attributes sb = sftp_lstat(mSftp, path.data());
if (sb == NULL) { if (sb == NULL) {
@ -531,6 +543,7 @@ void sftpProtocol::openConnection() {
<< ", info.url = " << info.url.prettyURL() << endl; << ", info.url = " << info.url.prettyURL() << endl;
if (checkCachedAuthentication(info)) { if (checkCachedAuthentication(info)) {
kdDebug() << "using cached" << endl;
mUsername = info.username; mUsername = info.username;
mPassword = info.password; mPassword = info.password;
} }
@ -708,78 +721,110 @@ void sftpProtocol::openConnection() {
rc = ssh_userauth_none(mSession, NULL); rc = ssh_userauth_none(mSession, NULL);
if (rc == SSH_AUTH_ERROR) { if (rc == SSH_AUTH_ERROR) {
closeConnection(); closeConnection();
error(TDEIO::ERR_COULD_NOT_LOGIN, i18n("Authentication failed.")); error(TDEIO::ERR_COULD_NOT_LOGIN, i18n("Authentication failed (method: %1).")
.arg(i18n("none")));
return; return;
} }
int method = ssh_auth_list(mSession); 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!"));
return;
}
bool firstTime = true; bool firstTime = true;
bool dlgResult; bool dlgResult;
while (rc != SSH_AUTH_SUCCESS) { while (rc != SSH_AUTH_SUCCESS) {
// Try to authenticate with public key first // Try to authenticate with public key first
kdDebug(TDEIO_SFTP_DB) << "Trying to authenticate public key" << endl; if (rc != SSH_AUTH_SUCCESS && (method & SSH_AUTH_METHOD_PUBLICKEY) && !mPassword)
if (method & SSH_AUTH_METHOD_PUBLICKEY) { {
rc = ssh_userauth_autopubkey(mSession, NULL); kdDebug(TDEIO_SFTP_DB) << "Trying to authenticate with public key" << endl;
if (rc == SSH_AUTH_ERROR) { for(;;)
closeConnection(); {
error(TDEIO::ERR_COULD_NOT_LOGIN, i18n("Authentication failed.")); rc = ssh_userauth_publickey_auto(mSession, nullptr, nullptr);
return; if (rc == SSH_AUTH_ERROR)
} else if (rc == SSH_AUTH_SUCCESS) { {
break; 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;
}
} }
} }
info.caption = i18n("SFTP Login");
info.readOnly = false;
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;
}
firstTime = false;
if (mUsername != info.username) {
kdDebug(TDEIO_SFTP_DB) << "Username changed from " << mUsername
<< " to " << info.username << endl;
}
mUsername = info.username;
mPassword = info.password;
// Try to authenticate with keyboard interactive // Try to authenticate with keyboard interactive
kdDebug(TDEIO_SFTP_DB) << "Trying to authenticate with keyboard interactive" << endl; if (rc != SSH_AUTH_SUCCESS && (method & SSH_AUTH_METHOD_INTERACTIVE))
if (method & SSH_AUTH_METHOD_INTERACTIVE) { {
rc = authenticateKeyboardInteractive(info); kdDebug(TDEIO_SFTP_DB) << "Trying to authenticate with keyboard interactive" << endl;
if (rc == SSH_AUTH_ERROR) {
TDEIO::AuthInfo tmpInfo(info);
rc = authenticateKeyboardInteractive(tmpInfo);
if (rc == SSH_AUTH_SUCCESS)
{
info = tmpInfo;
}
else if (rc == SSH_AUTH_ERROR)
{
closeConnection(); closeConnection();
error(TDEIO::ERR_COULD_NOT_LOGIN, i18n("Authentication failed.")); error(TDEIO::ERR_COULD_NOT_LOGIN, i18n("Authentication failed (method: %1).")
.arg(i18n("keyboard interactive")));
return; return;
} else if (rc == SSH_AUTH_SUCCESS) {
break;
} }
} }
// Try to authenticate with password // Try to authenticate with password
kdDebug(TDEIO_SFTP_DB) << "Trying to authenticate with password" << endl; if (rc != SSH_AUTH_SUCCESS && (method & SSH_AUTH_METHOD_PASSWORD))
if (method & SSH_AUTH_METHOD_PASSWORD) { {
rc = ssh_userauth_password(mSession, mUsername.utf8().data(), kdDebug(TDEIO_SFTP_DB) << "Trying to authenticate with password" << endl;
mPassword.utf8().data());
if (rc == SSH_AUTH_ERROR) { info.keepPassword = true;
closeConnection(); for(;;)
error(TDEIO::ERR_COULD_NOT_LOGIN, i18n("Authentication failed.")); {
return; if(!firstTime || mPassword.isEmpty())
} else if (rc == SSH_AUTH_SUCCESS) { {
break; 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;
}
firstTime = false;
}
if (mUsername != info.username) {
kdDebug(TDEIO_SFTP_DB) << "Username changed from " << mUsername
<< " to " << info.username << endl;
}
mUsername = info.username;
mPassword = info.password;
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")));
return;
} else if (rc == SSH_AUTH_SUCCESS) {
break;
}
} }
} }
} }
@ -947,12 +992,12 @@ void sftpProtocol::statMime(const KURL &url) {
void sftpProtocol::read(TDEIO::filesize_t bytes) { void sftpProtocol::read(TDEIO::filesize_t bytes) {
kdDebug(TDEIO_SFTP_DB) << "read, offset = " << openOffset << ", bytes = " << bytes; kdDebug(TDEIO_SFTP_DB) << "read, offset = " << openOffset << ", bytes = " << bytes;
ASSERT(mOpenFile != NULL); Q_ASSERT(mOpenFile != NULL);
TQVarLengthArray<char> buffer(bytes); TQVarLengthArray<char> buffer(bytes);
ssize_t bytesRead = sftp_read(mOpenFile, buffer.data(), bytes); ssize_t bytesRead = sftp_read(mOpenFile, buffer.data(), bytes);
ASSERT(bytesRead <= static_cast<ssize_t>(bytes)); Q_ASSERT(bytesRead <= static_cast<ssize_t>(bytes));
if (bytesRead < 0) { if (bytesRead < 0) {
kdDebug(TDEIO_SFTP_DB) << "Could not read " << mOpenUrl; kdDebug(TDEIO_SFTP_DB) << "Could not read " << mOpenUrl;
@ -968,7 +1013,7 @@ void sftpProtocol::read(TDEIO::filesize_t bytes) {
void sftpProtocol::write(const TQByteArray &data) { void sftpProtocol::write(const TQByteArray &data) {
kdDebug(TDEIO_SFTP_DB) << "write, offset = " << openOffset << ", bytes = " << data.size(); kdDebug(TDEIO_SFTP_DB) << "write, offset = " << openOffset << ", bytes = " << data.size();
ASSERT(mOpenFile != NULL); Q_ASSERT(mOpenFile != NULL);
ssize_t bytesWritten = sftp_write(mOpenFile, data.data(), data.size()); ssize_t bytesWritten = sftp_write(mOpenFile, data.data(), data.size());
if (bytesWritten < 0) { if (bytesWritten < 0) {
@ -984,7 +1029,7 @@ void sftpProtocol::write(const TQByteArray &data) {
void sftpProtocol::seek(TDEIO::filesize_t offset) { void sftpProtocol::seek(TDEIO::filesize_t offset) {
kdDebug(TDEIO_SFTP_DB) << "seek, offset = " << offset; kdDebug(TDEIO_SFTP_DB) << "seek, offset = " << offset;
ASSERT(mOpenFile != NULL); Q_ASSERT(mOpenFile != NULL);
if (sftp_seek64(mOpenFile, static_cast<uint64_t>(offset)) < 0) { if (sftp_seek64(mOpenFile, static_cast<uint64_t>(offset)) < 0) {
error(TDEIO::ERR_COULD_NOT_SEEK, mOpenUrl.path()); error(TDEIO::ERR_COULD_NOT_SEEK, mOpenUrl.path());
@ -1193,7 +1238,8 @@ void sftpProtocol::put(const KURL& url, int permissions, bool overwrite, bool re
dataReq(); // Request for data dataReq(); // Request for data
result = readData(buffer); result = readData(buffer);
if (result >= 0) { if (result >= 0 && buffer.size()) {
kdDebug(TDEIO_SFTP_DB) << TQString("Got %1 bytes of data").arg(buffer.size()) << endl;
if (dest.isEmpty()) { if (dest.isEmpty()) {
if (bMarkPartial) { if (bMarkPartial) {
kdDebug(TDEIO_SFTP_DB) << "Appending .part extension to " << dest_orig << endl; kdDebug(TDEIO_SFTP_DB) << "Appending .part extension to " << dest_orig << endl;
@ -1253,6 +1299,7 @@ void sftpProtocol::put(const KURL& url, int permissions, bool overwrite, bool re
} // dest.isEmpty } // dest.isEmpty
ssize_t bytesWritten = sftp_write(file, buffer.data(), buffer.size()); ssize_t bytesWritten = sftp_write(file, buffer.data(), buffer.size());
kdDebug(TDEIO_SFTP_DB) << TQString("Written %1 bytes").arg(bytesWritten) << endl;
if (bytesWritten < 0) { if (bytesWritten < 0) {
error(TDEIO::ERR_COULD_NOT_WRITE, dest_orig); error(TDEIO::ERR_COULD_NOT_WRITE, dest_orig);
result = -1; result = -1;
@ -1270,7 +1317,7 @@ void sftpProtocol::put(const KURL& url, int permissions, bool overwrite, bool re
sftp_attributes attr = sftp_stat(mSftp, dest.data()); sftp_attributes attr = sftp_stat(mSftp, dest.data());
if (bMarkPartial && attr != NULL) { if (bMarkPartial && attr != NULL) {
size_t size = config()->readEntry("MinimumKeepSize", DEFAULT_MINIMUM_KEEP_SIZE).toLong(); size_t size = config()->readLongNumEntry("MinimumKeepSize", DEFAULT_MINIMUM_KEEP_SIZE);
if (attr->size < size) { if (attr->size < size) {
sftp_unlink(mSftp, dest.data()); sftp_unlink(mSftp, dest.data());
} }
@ -1466,7 +1513,7 @@ void sftpProtocol::listDir(const KURL& url) {
sftp_attributes dirent = NULL; sftp_attributes dirent = NULL;
const TQString sDetails = metaData(TQString("details")); const TQString sDetails = metaData(TQString("details"));
const int details = sDetails.isEmpty() ? 2 : sDetails.toInt(); const int details = sDetails.isEmpty() ? 2 : sDetails.toInt();
TQList<TQByteArray> entryNames; TQValueList<TQByteArray> entryNames;
UDSEntry entry; UDSEntry entry;
kdDebug(TDEIO_SFTP_DB) << "readdir: " << path.data() << ", details: " << TQString::number(details) << endl; kdDebug(TDEIO_SFTP_DB) << "readdir: " << path.data() << ", details: " << TQString::number(details) << endl;
@ -1489,7 +1536,7 @@ void sftpProtocol::listDir(const KURL& url) {
entry.append(atom); entry.append(atom);
if (dirent->type == SSH_FILEXFER_TYPE_SYMLINK) { if (dirent->type == SSH_FILEXFER_TYPE_SYMLINK) {
TQCString file = (TQString(path) + "/" + TQFile::decodeName(dirent->name)).utf8().data(); TQCString file = (TQString::fromUtf8(path) + "/" + TQFile::decodeName(dirent->name)).utf8().data();
atom.m_uds = UDS_FILE_TYPE; atom.m_uds = UDS_FILE_TYPE;
atom.m_long = S_IFREG; atom.m_long = S_IFREG;
@ -1771,3 +1818,11 @@ void sftpProtocol::slave_status() {
slaveStatus((mConnected ? mHost : TQString()), mConnected); slaveStatus((mConnected ? mHost : TQString()), mConnected);
} }
void sftpProtocol::clearPubKeyAuthInfo()
{
if (!pubKeyInfo)
{
delete pubKeyInfo;
pubKeyInfo = nullptr;
}
}

@ -70,12 +70,13 @@ public:
// libssh authentication callback (note that this is called by the // libssh authentication callback (note that this is called by the
// global ::auth_callback() call. // global ::auth_callback() call.
int auth_callback(const char *prompt, char *buf, size_t len, int auth_callback(const char *prompt, char *buf, size_t len,
int echo, int verify, void *userdata); int echo, int verify, void *userdata);
// libssh logging callback (note that this is called by the // libssh logging callback (note that this is called by the
// global ::log_callback() call. // global ::log_callback() call.
void log_callback(ssh_session session, int priority, const char *message, void log_callback(ssh_session session, int priority, const char *message,
void *userdata); void *userdata);
private: // Private variables private: // Private variables
void statMime(const KURL &url); void statMime(const KURL &url);
@ -119,9 +120,12 @@ private: // Private variables
// TQString text; // TQString text;
//}; //};
TDEIO::AuthInfo *pubKeyInfo;
private: // private methods private: // private methods
int authenticateKeyboardInteractive(TDEIO::AuthInfo &info); int authenticateKeyboardInteractive(TDEIO::AuthInfo &info);
void clearPubKeyAuthInfo();
void reportError(const KURL &url, const int err); void reportError(const KURL &url, const int err);

Loading…
Cancel
Save