File kio_sftp_rewrite.patch of Package kdebase4-runtime
Index: runtime/cmake/modules/FindPackageVersionCheck.cmake
===================================================================
--- runtime/cmake/modules/FindPackageVersionCheck.cmake (revision 0)
+++ runtime/cmake/modules/FindPackageVersionCheck.cmake (revision 0)
@@ -0,0 +1,68 @@
+# FIND_PACKAGE_VERSION_CHECK(NAME (DEFAULT_MSG|"Custom failure message"))
+# This function is intended to be used in FindXXX.cmake modules files.
+# It handles NAME_FIND_VERSION and NAME_VERSION variables in a Module.
+#
+# Example:
+# find_package(LibSSH 0.3.2)
+#
+# # check for the version and set it
+# set(LibSSH_VERSION 0.3.0)
+# find_package_version_check(LibSSH DEFAULT_MSG)
+#
+#
+# Copyright (c) 2009 Andreas Schneider <mail@cynapses.org>
+#
+# Redistribution and use is allowed according to the terms of the New
+# BSD license.
+
+function(FIND_PACKAGE_VERSION_CHECK _NAME _FAIL_MSG)
+ string(TOUPPER ${_NAME} _NAME_UPPER)
+ set(_AGE "old")
+
+ if(${_NAME}_FIND_VERSION_EXACT)
+ if (${_NAME}_FIND_VERSION VERSION_EQUAL ${_NAME}_VERSION)
+ # exact version found
+ set(${_NAME_UPPER}_FOUND TRUE)
+ else (${_NAME}_FIND_VERSION VERSION_EQUAL ${_NAME}_VERSION)
+ # exect version not found
+ set(${_NAME_UPPER}_FOUND FALSE)
+ # check if newer or older
+ if (${_NAME}_FIND_VERSION VERSION_LESS ${_NAME}_VERSION)
+ set(_AGE "new")
+ else (${_NAME}_FIND_VERSION VERSION_LESS ${_NAME}_VERSION)
+ set(_AGE "old")
+ endif (${_NAME}_FIND_VERSION VERSION_LESS ${_NAME}_VERSION)
+ endif (${_NAME}_FIND_VERSION VERSION_EQUAL ${_NAME}_VERSION)
+ else (${_NAME}_FIND_VERSION_EXACT)
+ if (${_NAME}_FIND_VERSION)
+ if (${_NAME}_VERSION VERSION_LESS ${_NAME}_FIND_VERSION)
+ set(${_NAME_UPPER}_FOUND FALSE)
+ set(_AGE "old")
+ else (${_NAME}_VERSION VERSION_LESS ${_NAME}_FIND_VERSION)
+ set(${_NAME_UPPER}_FOUND TRUE)
+ endif (${_NAME}_VERSION VERSION_LESS ${_NAME}_FIND_VERSION)
+ endif (${_NAME}_FIND_VERSION)
+ endif(${_NAME}_FIND_VERSION_EXACT)
+
+ if ("${_FAIL_MSG}" STREQUAL "DEFAULT_MSG")
+ if (${_NAME}_FIND_VERSION_EXACT)
+ set(_FAIL_MESSAGE "The installed ${_NAME} version ${${_NAME}_VERSION} is too ${_AGE}, version ${${_NAME}_FIND_VERSION} is required.")
+ else (${_NAME}_FIND_VERSION_EXACT)
+ set(_FAIL_MESSAGE "The installed ${_NAME} version ${${_NAME}_VERSION} is too ${_AGE}, at least version ${${_NAME}_FIND_VERSION} is required.")
+ endif (${_NAME}_FIND_VERSION_EXACT)
+ else ("${_FAIL_MSG}" STREQUAL "DEFAULT_MSG")
+ set(_FAIL_MESSAGE "${_FAIL_MSG}")
+ endif ("${_FAIL_MSG}" STREQUAL "DEFAULT_MSG")
+
+ if (NOT ${_NAME_UPPER}_FOUND)
+ if (${_NAME}_FIND_REQUIRED)
+ message(FATAL_ERROR "${_FAIL_MESSAGE}")
+ else (${_NAME}_FIND_REQUIRED)
+ if (NOT ${_NAME}_FIND_QUIETLY)
+ message(STATUS "${_FAIL_MESSAGE}")
+ endif (NOT ${_NAME}_FIND_QUIETLY)
+ endif (${_NAME}_FIND_REQUIRED)
+ endif (NOT ${_NAME_UPPER}_FOUND)
+
+ set(${_NAME_UPPER}_FOUND ${${_NAME_UPPER}_FOUND} PARENT_SCOPE)
+endfunction(FIND_PACKAGE_VERSION_CHECK)
Index: runtime/cmake/modules/FindLibSSH.cmake
===================================================================
--- runtime/cmake/modules/FindLibSSH.cmake (revision 0)
+++ runtime/cmake/modules/FindLibSSH.cmake (revision 0)
@@ -0,0 +1,92 @@
+# - Try to find LibSSH
+# Once done this will define
+#
+# LIBSSH_FOUND - system has LibSSH
+# LIBSSH_INCLUDE_DIRS - the LibSSH include directory
+# LIBSSH_LIBRARIES - Link these to use LibSSH
+# LIBSSH_DEFINITIONS - Compiler switches required for using LibSSH
+#
+# Copyright (c) 2009 Andreas Schneider <mail@cynapses.org>
+#
+# Redistribution and use is allowed according to the terms of the New
+# BSD license.
+# For details see the accompanying COPYING-CMAKE-SCRIPTS file.
+#
+
+if (LIBSSH_LIBRARIES AND LIBSSH_INCLUDE_DIRS)
+ # in cache already
+ set(LIBSSH_FOUND TRUE)
+else (LIBSSH_LIBRARIES AND LIBSSH_INCLUDE_DIRS)
+
+ find_path(LIBSSH_INCLUDE_DIR
+ NAMES
+ libssh/libssh.h
+ PATHS
+ /usr/include
+ /usr/local/include
+ /opt/local/include
+ /sw/include
+ )
+
+ find_library(SSH_LIBRARY
+ NAMES
+ ssh
+ libssh
+ PATHS
+ /usr/lib
+ /usr/local/lib
+ /opt/local/lib
+ /sw/lib
+ )
+
+ if (LIBSSH_INCLUDE_DIR AND SSH_LIBRARY)
+ set(SSH_FOUND TRUE)
+ endif (LIBSSH_INCLUDE_DIR AND SSH_LIBRARY)
+
+ set(LIBSSH_INCLUDE_DIRS
+ ${LIBSSH_INCLUDE_DIR}
+ )
+
+ if (SSH_FOUND)
+ set(LIBSSH_LIBRARIES
+ ${LIBSSH_LIBRARIES}
+ ${SSH_LIBRARY}
+ )
+
+ if (LibSSH_FIND_VERSION)
+ file(STRINGS ${LIBSSH_INCLUDE_DIR}/libssh/libssh.h LIBSSH_VERSION_MAJOR
+ REGEX "#define[ ]+LIBSSH_VERSION_MAJOR[ ]+[0-9]+")
+ # Older versions of libssh like libssh-0.2 have LIBSSH_VERSION but not LIBSSH_VERSION_MAJOR
+ if (LIBSSH_VERSION_MAJOR)
+ string(REGEX MATCH "[0-9]+" LIBSSH_VERSION_MAJOR ${LIBSSH_VERSION_MAJOR})
+ file(STRINGS ${LIBSSH_INCLUDE_DIR}/libssh/libssh.h LIBSSH_VERSION_MINOR
+ REGEX "#define[ ]+LIBSSH_VERSION_MINOR[ ]+[0-9]+")
+ string(REGEX MATCH "[0-9]+" LIBSSH_VERSION_MINOR ${LIBSSH_VERSION_MINOR})
+ file(STRINGS ${LIBSSH_INCLUDE_DIR}/libssh/libssh.h LIBSSH_VERSION_PATCH
+ REGEX "#define[ ]+LIBSSH_VERSION_MICRO[ ]+[0-9]+")
+ string(REGEX MATCH "[0-9]+" LIBSSH_VERSION_PATCH ${LIBSSH_VERSION_PATCH})
+
+ set(LibSSH_VERSION ${LIBSSH_VERSION_MAJOR}.${LIBSSH_VERSION_MINOR}.${LIBSSH_VERSION_PATCH})
+
+ include(FindPackageVersionCheck)
+ find_package_version_check(LibSSH DEFAULT_MSG)
+ else (LIBSSH_VERSION_MAJOR)
+ message(STATUS "LIBSSH_VERSION_MAJOR not found in ${LIBSSH_INCLUDE_DIR}/libssh/libssh.h, assuming libssh is too old")
+ set(LIBSSH_FOUND FALSE)
+ endif (LIBSSH_VERSION_MAJOR)
+ endif (LibSSH_FIND_VERSION)
+ endif (SSH_FOUND)
+
+ # If the version is too old, but libs and includes are set,
+ # find_package_handle_standard_args will set LIBSSH_FOUND to TRUE again,
+ # so we need this if() here.
+ if (LIBSSH_FOUND)
+ include(FindPackageHandleStandardArgs)
+ find_package_handle_standard_args(LibSSH DEFAULT_MSG LIBSSH_LIBRARIES LIBSSH_INCLUDE_DIRS)
+ endif (LIBSSH_FOUND)
+
+ # show the LIBSSH_INCLUDE_DIRS and LIBSSH_LIBRARIES variables only in the advanced view
+ mark_as_advanced(LIBSSH_INCLUDE_DIRS LIBSSH_LIBRARIES)
+
+endif (LIBSSH_LIBRARIES AND LIBSSH_INCLUDE_DIRS)
+
Index: runtime/kioslave/sftp/AUTHORS
===================================================================
--- runtime/kioslave/sftp/AUTHORS (revision 1026868)
+++ runtime/kioslave/sftp/AUTHORS (working copy)
@@ -1,3 +1,3 @@
+Andreas Schneider <mail@cynapses.org>
Dawit Alemayehu <adawit@kde.org>
Lucas Fisher <ljfisher@iastate.edu>
-
Index: runtime/kioslave/sftp/CHANGELOG
===================================================================
--- runtime/kioslave/sftp/CHANGELOG (revision 1026868)
+++ runtime/kioslave/sftp/CHANGELOG (working copy)
@@ -1,3 +1,8 @@
+2009-08-01 - Rewrite with libssh.
+ * Resume support
+ * Support for different details
+ * Mimetype support
+
- add dialog to ask for username
- rename() causes SSH to die
- How to handle overwrite?
Index: runtime/kioslave/sftp/kio_sftp.cpp
===================================================================
--- runtime/kioslave/sftp/kio_sftp.cpp (revision 1026868)
+++ runtime/kioslave/sftp/kio_sftp.cpp (working copy)
@@ -1,27 +1,23 @@
-/***************************************************************************
- sftp.cpp - description
- -------------------
- begin : Fri Jun 29 23:45:40 CDT 2001
- copyright : (C) 2001 by Lucas Fisher
- email : ljfisher@purdue.edu
- ***************************************************************************/
-
-/***************************************************************************
- * *
- * This program is free software; you can redistribute it and/or modify *
- * it under the terms of the GNU General Public License as published by *
- * the Free Software Foundation; either version 2 of the License, or *
- * (at your option) any later version. *
- * *
- ***************************************************************************/
-
/*
-DEBUGGING
-We are pretty much left with kDebug messages for debugging. We can't use a gdb
-as described in the ioslave DEBUG.howto because kdeinit has to run in a terminal.
-Ssh will detect this terminal and ask for a password there, but will just get garbage.
-So we can't connect.
-*/
+ * Copyright (c) 2001 Lucas Fisher <ljfisher@purdue.edu>
+ * Copyright (c) 2009 Andreas Schneider <mail@cynapses.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 "kio_sftp.h"
@@ -32,25 +28,21 @@
#include <QtCore/QCoreApplication>
#include <QtCore/QBuffer>
#include <QtCore/QByteArray>
+#include <QtCore/QDir>
#include <QtCore/QFile>
#include <QtCore/QObject>
#include <QtCore/QString>
+#include <QtCore/QVarLengthArray>
#include <stdlib.h>
#include <unistd.h>
-#include <signal.h>
#include <errno.h>
-#include <ctype.h>
#include <time.h>
-#include <netdb.h>
#include <string.h>
-#include <netinet/in.h>
-#include <arpa/inet.h>
#include <sys/time.h>
#include <sys/stat.h>
#include <sys/types.h>
-#include <sys/wait.h>
#include <kapplication.h>
#include <kuser.h>
@@ -64,15 +56,11 @@
#include <kio/ioslave_defaults.h>
#include <kmimetype.h>
#include <kde_file.h>
-#include <kremoteencoding.h>
#include <kconfiggroup.h>
-#include "sftp.h"
-#include "atomicio.h"
-#include "sftpfileattr.h"
-#include "ksshprocess.h"
+#include <libssh/libssh.h>
+#include <libssh/sftp.h>
-
using namespace KIO;
extern "C"
{
@@ -96,2293 +84,1456 @@
}
}
+// 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;
+ }
-/*
- * This helper handles some special issues (blocking and interrupted
- * system call) when writing to a file handle.
- *
- * @return 0 on success or an error code on failure (ERR_COULD_NOT_WRITE,
- * ERR_DISK_FULL, ERR_CONNECTION_BROKEN).
- */
-static int writeToFile (int fd, const char *buf, size_t len)
-{
- while (len > 0)
- {
- ssize_t written = ::write(fd, buf, len);
- if (written >= 0)
- {
- buf += written;
- len -= written;
- continue;
- }
+ sftpProtocol *slave = (sftpProtocol *) userdata;
- switch(errno)
- {
- case EINTR:
- continue;
- case EPIPE:
- return ERR_CONNECTION_BROKEN;
- case ENOSPC:
- return ERR_DISK_FULL;
- default:
- return ERR_COULD_NOT_WRITE;
- }
+ if (slave->auth_callback(prompt, buf, len, echo, verify, userdata) < 0) {
+ return -1;
}
+
return 0;
}
-sftpProtocol::sftpProtocol(const QByteArray &pool_socket, const QByteArray &app_socket)
- : SlaveBase("kio_sftp", pool_socket, app_socket),
- mConnected(false), mPort(-1), mMsgId(0) {
-#ifndef Q_WS_WIN
- kDebug(KIO_SFTP_DB) << "pid = " << getpid();
-#endif
-}
+int sftpProtocol::auth_callback(const char *prompt, char *buf, size_t len,
+ int echo, int verify, void *userdata) {
+ QString i_prompt = QString::fromUtf8(prompt);
+ // unused variables
+ (void) echo;
+ (void) verify;
+ (void) userdata;
-sftpProtocol::~sftpProtocol() {
-#ifndef Q_WS_WIN
- kDebug(KIO_SFTP_DB) << "pid = " << getpid();
-#endif
- closeConnection();
-}
+ kDebug(KIO_SFTP_DB) << "Entering authentication callback, prompt=" << i_prompt;
-/**
- * Type is a sftp packet type found in .sftp.h'.
- * Example: SSH2_FXP_READLINK, SSH2_FXP_RENAME, etc.
- *
- * Returns true if the type is supported by the sftp protocol
- * version negotiated by the client and server (sftpVersion).
- */
-bool sftpProtocol::isSupportedOperation(int type) {
- switch (type) {
- case SSH2_FXP_VERSION:
- case SSH2_FXP_STATUS:
- case SSH2_FXP_HANDLE:
- case SSH2_FXP_DATA:
- case SSH2_FXP_NAME:
- case SSH2_FXP_ATTRS:
- case SSH2_FXP_INIT:
- case SSH2_FXP_OPEN:
- case SSH2_FXP_CLOSE:
- case SSH2_FXP_READ:
- case SSH2_FXP_WRITE:
- case SSH2_FXP_LSTAT:
- case SSH2_FXP_FSTAT:
- case SSH2_FXP_SETSTAT:
- case SSH2_FXP_FSETSTAT:
- case SSH2_FXP_OPENDIR:
- case SSH2_FXP_READDIR:
- case SSH2_FXP_REMOVE:
- case SSH2_FXP_MKDIR:
- case SSH2_FXP_RMDIR:
- case SSH2_FXP_REALPATH:
- case SSH2_FXP_STAT:
- return true;
- case SSH2_FXP_RENAME:
- return sftpVersion >= 2 ? true : false;
- case SSH2_FXP_EXTENDED:
- case SSH2_FXP_EXTENDED_REPLY:
- case SSH2_FXP_READLINK:
- case SSH2_FXP_SYMLINK:
- return sftpVersion >= 3 ? true : false;
- default:
- kDebug(KIO_SFTP_DB) << "isSupportedOperation(type:"
- << type << "): unrecognized operation type";
- break;
- }
+ KIO::AuthInfo info;
- return false;
-}
+ info.url.setProtocol("sftp");
+ info.url.setHost(mHost);
+ info.url.setPort(mPort);
+ info.url.setUser(mUsername);
-void sftpProtocol::copy(const KUrl &src, const KUrl &dest, int permissions, KIO::JobFlags flags)
-{
- kDebug(KIO_SFTP_DB) << src << " -> " << dest;
+ info.comment = "sftp://" + mUsername + "@" + mHost;
+ info.username = QString("UNUSED");
+ info.readOnly = true;
+ info.prompt = i_prompt;
+ info.keepPassword = false;
- bool srcLocal = src.isLocalFile();
- bool destLocal = dest.isLocalFile();
+ if (!openPasswordDialog(info)) {
+ kDebug(KIO_SFTP_DB) << "Password dialog failed";
+ return -1;
+ }
- if ( srcLocal && !destLocal ) // Copy file -> sftp
- sftpCopyPut(src, dest, permissions, flags);
- else if ( destLocal && !srcLocal ) // Copy sftp -> file
- sftpCopyGet(dest, src, permissions, flags);
- else
- error(ERR_UNSUPPORTED_ACTION, QString());
+ strncpy(buf, info.password.toUtf8().constData(), len - 1);
+
+ info.password.fill('x');
+
+ return 0;
}
-void sftpProtocol::sftpCopyGet(const KUrl& dest, const KUrl& src, int mode, KIO::JobFlags flags)
-{
- kDebug(KIO_SFTP_DB) << src << " -> " << dest;
+int sftpProtocol::authenticateKeyboardInteractive(AuthInfo &info) {
+ QString name, instruction, prompt;
+ int err = SSH_AUTH_ERROR;
- // Attempt to establish a connection...
- openConnection();
- if( !mConnected )
- return;
+ kDebug(KIO_SFTP_DB) << "Entering keyboard interactive function";
- KDE_struct_stat buff_orig;
- const QString dest_orig = dest.path();
- bool origExists = (KDE::lstat( dest_orig, &buff_orig ) != -1);
+ err = ssh_userauth_kbdint(ssh_session, mUsername.toUtf8().constData(), NULL);
+ while (err == SSH_AUTH_INFO) {
+ int n = 0;
+ int i = 0;
- if (origExists)
- {
- if (S_ISDIR(buff_orig.st_mode))
- {
- error(ERR_IS_DIRECTORY, dest.prettyUrl());
- return;
- }
+ name = QString::fromUtf8(ssh_userauth_kbdint_getname(ssh_session));
+ instruction = QString::fromUtf8(ssh_userauth_kbdint_getinstruction(ssh_session));
+ n = ssh_userauth_kbdint_getnprompts(ssh_session);
- if (!(flags & KIO::Overwrite))
- {
- error(ERR_FILE_ALREADY_EXIST, dest.prettyUrl());
- return;
- }
- }
+ kDebug(KIO_SFTP_DB) << "name=" << name << " instruction=" << instruction
+ << " prompts" << n;
- KIO::filesize_t offset = 0;
- QString dest_part(dest_orig + ".part");
+ for (i = 0; i < n; ++i) {
+ char echo;
+ const char *answer = "";
- int fd = -1;
- bool partExists = false;
- bool markPartial = config()->readEntry("MarkPartial", true);
+ prompt = QString::fromUtf8(ssh_userauth_kbdint_getprompt(ssh_session, i, &echo));
+ kDebug(KIO_SFTP_DB) << "prompt=" << prompt << " echo=" << QString::number(echo);
+ if (echo) {
+ // See RFC4256 Section 3.3 User Interface
+ QString newPrompt;
+ KIO::AuthInfo infoKbdInt;
- if (markPartial)
- {
- KDE_struct_stat buff_part;
- partExists = (KDE::stat(dest_part, &buff_part ) != -1);
+ infoKbdInt.url.setProtocol("sftp");
+ infoKbdInt.url.setHost(mHost);
+ infoKbdInt.url.setPort(mPort);
- if (partExists && buff_part.st_size > 0 && S_ISREG(buff_part.st_mode))
- {
- if (canResume( buff_part.st_size ))
- {
- offset = buff_part.st_size;
- kDebug(KIO_SFTP_DB) << "Resuming @ " << offset;
- }
+ infoKbdInt.caption = i18n("SFTP Login");
+ infoKbdInt.comment = "sftp://" + mUsername + "@" + mHost;
+
+ if (!name.isEmpty()) {
+ infoKbdInt.caption = QString(i18n("SFTP Login") + " - " + name);
}
- if (offset > 0)
- {
- fd = KDE::open(dest_part, O_RDWR);
- offset = KDE_lseek(fd, 0, SEEK_END);
- if (offset == 0)
- {
- error(ERR_CANNOT_RESUME, dest.prettyUrl());
- return;
- }
+ if (!instruction.isEmpty()) {
+ newPrompt = instruction + "\n\n";
}
- else
- {
- // Set up permissions properly, based on what is done in file io-slave
- int openFlags = (O_CREAT | O_TRUNC | O_WRONLY);
- int initialMode = (mode == -1) ? 0666 : (mode | S_IWUSR);
- fd = KDE::open(dest_part, openFlags, initialMode);
- }
- }
- else
- {
- // Set up permissions properly, based on what is done in file io-slave
- int openFlags = (O_CREAT | O_TRUNC | O_WRONLY);
- int initialMode = (mode == -1) ? 0666 : (mode | S_IWUSR);
- fd = KDE::open(dest_orig, openFlags, initialMode);
- }
- if(fd == -1)
- {
- kDebug(KIO_SFTP_DB) << "Unable to open (" << fd << ") for writing.";
- if (errno == EACCES)
- error (ERR_WRITE_ACCESS_DENIED, dest.prettyUrl());
- else
- error (ERR_CANNOT_OPEN_FOR_WRITING, dest.prettyUrl());
- return;
- }
+ newPrompt.append(prompt + "\n\n");
+ infoKbdInt.readOnly = false;
+ infoKbdInt.keepPassword = false;
- Status info = sftpGet(src, offset, fd);
- if ( info.code != 0 )
- {
- // Should we keep the partially downloaded file ??
- KIO::filesize_t size = config()->readEntry("MinimumKeepSize", DEFAULT_MINIMUM_KEEP_SIZE);
- if (info.size < size)
- QFile::remove(dest_part);
+ if (openPasswordDialog(infoKbdInt, i18n("Use the username input field to answer this question!"))) {
+ kDebug(KIO_SFTP_DB) << "Got the answer from the password dialog";
+ answer = info.username.toUtf8().constData();
+ }
- error(info.code, info.text);
- return;
- }
+ if (ssh_userauth_kbdint_setanswer(ssh_session, i, answer) < 0) {
+ kDebug(KIO_SFTP_DB) << "An error occured setting the answer: "
+ << ssh_get_error(ssh_session);
+ return SSH_AUTH_ERROR;
+ }
+ break;
+ } else {
+ if (prompt.contains("Password", Qt::CaseInsensitive)) {
+ answer = mPassword.toUtf8().constData();
+ } else {
+ info.readOnly = true; // set username readonly
+ info.prompt = prompt;
+ info.keepPassword = false;
- if (::close(fd) != 0)
- {
- error(ERR_COULD_NOT_WRITE, dest.prettyUrl());
- return;
- }
+ if (openPasswordDialog(info)) {
+ kDebug(KIO_SFTP_DB) << "Got the answer from the password dialog";
+ answer = info.password.toUtf8().constData();
+ }
+ }
- //
- if (markPartial)
- {
- if (KDE::rename(dest_part, dest_orig) != 0)
- {
- error (ERR_CANNOT_RENAME_PARTIAL, dest_part);
- return;
+ if (ssh_userauth_kbdint_setanswer(ssh_session, i, answer) < 0) {
+ kDebug(KIO_SFTP_DB) << "An error occured setting the answer: "
+ << ssh_get_error(ssh_session);
+ return SSH_AUTH_ERROR;
+ }
}
}
+ err = ssh_userauth_kbdint(ssh_session, mUsername.toUtf8().constData(), NULL);
+ }
- data(QByteArray());
- kDebug(KIO_SFTP_DB) << "emit finished()";
- finished();
+ return err;
}
-sftpProtocol::Status sftpProtocol::sftpGet( const KUrl& src, KIO::filesize_t offset, int fd, bool abortAfterMimeType )
-{
- int code;
- sftpFileAttr attr(remoteEncoding());
+void sftpProtocol::reportError(const KUrl &url, const int err) {
+ kDebug(KIO_SFTP_DB) << "url = " << url << " - err=" << err;
- Status res;
- res.code = 0;
- res.size = 0;
+ switch (err) {
+ case SSH_FX_OK:
+ break;
+ case SSH_FX_NO_SUCH_FILE:
+ case SSH_FX_NO_SUCH_PATH:
+ error(ERR_DOES_NOT_EXIST, url.prettyUrl());
+ break;
+ case SSH_FX_PERMISSION_DENIED:
+ error(ERR_ACCESS_DENIED, url.prettyUrl());
+ break;
+ case SSH_FX_FILE_ALREADY_EXISTS:
+ error(ERR_FILE_ALREADY_EXIST, url.prettyUrl());
+ break;
+ case SSH_FX_INVALID_HANDLE:
+ error(ERR_MALFORMED_URL, url.prettyUrl());
+ break;
+ case SSH_FX_OP_UNSUPPORTED:
+ error(ERR_UNSUPPORTED_ACTION, url.prettyUrl());
+ break;
+ case SSH_FX_BAD_MESSAGE:
+ error(ERR_UNKNOWN, url.prettyUrl());
+ break;
+ default:
+ error(ERR_INTERNAL, url.prettyUrl());
+ break;
+ }
+}
- kDebug(KIO_SFTP_DB) << src;
+bool sftpProtocol::createUDSEntry(const QString &filename, const QByteArray &path,
+ UDSEntry &entry, short int details) {
+ mode_t type;
+ mode_t access;
+ char *link;
- // stat the file first to get its size
- if( (code = sftpStat(src, attr)) != SSH2_FX_OK ) {
- return doProcessStatus(code, src.prettyUrl());
- }
+ Q_ASSERT(entry.count() == 0);
- // We cannot get file if it is a directory
- if( attr.fileType() == S_IFDIR ) {
- res.text = src.prettyUrl();
- res.code = ERR_IS_DIRECTORY;
- return res;
- }
+ SFTP_ATTRIBUTES *sb = sftp_lstat(sftp_session, path.constData());
+ if (sb == NULL) {
+ return false;
+ }
- KIO::filesize_t fileSize = attr.fileSize();
- quint32 pflags = SSH2_FXF_READ;
- attr.clear();
+ entry.insert(KIO::UDSEntry::UDS_NAME, filename);
- QByteArray handle;
- if( (code = sftpOpen(src, pflags, attr, handle)) != SSH2_FX_OK ) {
- res.text = src.prettyUrl();
- res.code = ERR_CANNOT_OPEN_FOR_READING;
- return res;
+ if (sb->type == SSH_FILEXFER_TYPE_SYMLINK) {
+ entry.insert(KIO::UDSEntry::UDS_FILE_TYPE, S_IFREG);
+ link = sftp_readlink(sftp_session, path.constData());
+ if (link == NULL) {
+ sftp_attributes_free(sb);
+ return false;
}
+ entry.insert(KIO::UDSEntry::UDS_LINK_DEST, QFile::decodeName(link));
+ delete link;
+ // A symlink -> follow it only if details > 1
+ if (details > 1) {
+ SFTP_ATTRIBUTES *sb2 = sftp_stat(sftp_session, path.constData());
+ if (sb2 == NULL) {
+ // It is a link pointing to nowhere
+ type = S_IFMT - 1;
+ access = S_IRWXU | S_IRWXG | S_IRWXO;
+ entry.insert( KIO::UDSEntry::UDS_FILE_TYPE, type);
+ entry.insert( KIO::UDSEntry::UDS_ACCESS, access);
+ entry.insert( KIO::UDSEntry::UDS_SIZE, 0LL );
- // needed for determining mimetype
- // note: have to emit mimetype before emitting totalsize.
- QByteArray buff;
- QByteArray mimeBuffer;
+ goto notype;
+ }
+ sftp_attributes_free(sb);
+ sb = sb2;
+ }
+ }
- unsigned int oldSize;
- bool foundMimetype = false;
+ switch (sb->type) {
+ case SSH_FILEXFER_TYPE_REGULAR:
+ entry.insert(KIO::UDSEntry::UDS_FILE_TYPE, S_IFREG);
+ break;
+ case SSH_FILEXFER_TYPE_DIRECTORY:
+ entry.insert(KIO::UDSEntry::UDS_FILE_TYPE, S_IFDIR);
+ break;
+ case SSH_FILEXFER_TYPE_SYMLINK:
+ entry.insert(KIO::UDSEntry::UDS_FILE_TYPE, S_IFLNK);
+ break;
+ case SSH_FILEXFER_TYPE_SPECIAL:
+ case SSH_FILEXFER_TYPE_UNKNOWN:
+ entry.insert(KIO::UDSEntry::UDS_FILE_TYPE, S_IFMT - 1);
+ break;
+ }
- // How big should each data packet be? Definitely not bigger than 64kb or
- // you will overflow the 2 byte size variable in a sftp packet.
- quint32 len = 60*1024;
- code = SSH2_FX_OK;
+ access = sb->permissions & 07777;
+ entry.insert(KIO::UDSEntry::UDS_ACCESS, access);
- kDebug(KIO_SFTP_DB) << "offset = " << offset;
- while( code == SSH2_FX_OK ) {
- if( (code = sftpRead(handle, offset, len, buff)) == SSH2_FX_OK ) {
- offset += buff.size();
+ entry.insert(KIO::UDSEntry::UDS_SIZE, sb->size);
- // save data for mimetype. Pretty much follows what is in the ftp ioslave
- if( !foundMimetype ) {
- oldSize = mimeBuffer.size();
- mimeBuffer.resize(oldSize + buff.size());
- memcpy(mimeBuffer.data()+oldSize, buff.data(), buff.size());
-
- if( mimeBuffer.size() > 1024 || offset == fileSize ) {
- // determine mimetype
- KMimeType::Ptr mime = KMimeType::findByNameAndContent(src.fileName(), mimeBuffer);
- kDebug(KIO_SFTP_DB) << "mimetype is " << mime->name();
- mimeType(mime->name());
-
- if (abortAfterMimeType)
- break;
-
- // Always send the total size after emitting mime-type...
- totalSize(fileSize);
-
- if (fd == -1)
- data(mimeBuffer);
- else
- {
- if ( (res.code=writeToFile(fd, mimeBuffer.data(), mimeBuffer.size())) != 0 )
- return res;
- }
-
- processedSize(mimeBuffer.size());
- mimeBuffer.resize(0);
- foundMimetype = true;
- }
- }
- else {
- if (fd == -1)
- data(buff);
- else
- {
- if ( (res.code= writeToFile(fd, buff.data(), buff.size())) != 0 )
- return res;
- }
- processedSize(offset);
- }
- }
-
- /*
- Check if slave was killed. According to slavebase.h we need to leave
- the slave methods as soon as possible if the slave is killed. This
- allows the slave to be cleaned up properly.
- */
- if( wasKilled() ) {
- res.text = i18n("An internal error occurred. Please retry the request again.");
- res.code = ERR_UNKNOWN;
- return res;
- }
+notype:
+ if (details > 0) {
+ if (sb->owner) {
+ entry.insert(KIO::UDSEntry::UDS_USER, sb->owner);
+ } else {
+ entry.insert(KIO::UDSEntry::UDS_USER, mUsername);
}
- if( code != SSH2_FX_EOF && !abortAfterMimeType ) {
- res.text = src.prettyUrl();
- res.code = ERR_COULD_NOT_READ; // return here or still send empty array to indicate end of read?
+ if (sb->group) {
+ entry.insert(KIO::UDSEntry::UDS_GROUP, sb->group);
+ } else {
+ entry.insert(KIO::UDSEntry::UDS_GROUP, "users");
}
- res.size = offset;
- sftpClose(handle);
- processedSize (offset);
- return res;
-}
+ entry.insert(KIO::UDSEntry::UDS_ACCESS_TIME, sb->atime);
+ entry.insert(KIO::UDSEntry::UDS_MODIFICATION_TIME, sb->mtime);
+ entry.insert(KIO::UDSEntry::UDS_CREATION_TIME, sb->createtime);
+ }
-void sftpProtocol::get(const KUrl& url) {
- kDebug(KIO_SFTP_DB) << "get(): " << url;
+ sftp_attributes_free(sb);
- openConnection();
- if( !mConnected )
- return;
-
- // Get resume offset
- quint64 offset = config()->readEntry("resume",0);
- if( offset > 0 ) {
- canResume();
- kDebug(KIO_SFTP_DB) << "get(): canResume(), offset = " << offset;
- }
-
- Status info = sftpGet(url, offset);
-
- if (info.code != 0)
- {
- error(info.code, info.text);
- return;
- }
-
- data(QByteArray());
- kDebug(KIO_SFTP_DB) << "get(): emit finished()";
- finished();
+ return true;
}
+QString sftpProtocol::canonicalizePath(const QString &path) {
+ kDebug(KIO_SFTP_DB) << "Path to canonicalize: " << path;
+ QString cPath;
+ char *sPath = NULL;
-void sftpProtocol::setHost (const QString& h, quint16 port, const QString& user, const QString& pass)
-{
- kDebug(KIO_SFTP_DB) << "setHost(): " << user << "@" << h << ":" << port;
+ if (path.isEmpty()) {
+ return cPath;
+ }
- if( mHost != h || mPort != port || user != mUsername || mPassword != pass )
- closeConnection();
+ sPath = sftp_canonicalize_path(sftp_session, path.toUtf8().constData());
+ if (sPath == NULL) {
+ kDebug(KIO_SFTP_DB) << "Could not canonicalize path: " << path;
+ return cPath;
+ }
- mHost = h;
+ cPath = QFile::decodeName(sPath);
+ delete sPath;
- if( port > 0 )
- mPort = port;
- else {
- struct servent *pse;
- if( (pse = getservbyname("ssh", "tcp") ) == NULL )
- mPort = 22;
- else
- mPort = ntohs(pse->s_port);
- }
+ kDebug(KIO_SFTP_DB) << "Canonicalized path: " << cPath;
- mUsername = user;
- mPassword = pass;
+ return cPath;
+}
- if (user.isEmpty())
- {
- KUser u;
- mUsername = u.loginName();
- }
+sftpProtocol::sftpProtocol(const QByteArray &pool_socket, const QByteArray &app_socket)
+ : SlaveBase("kio_sftp", pool_socket, app_socket),
+ mConnected(false), mPort(-1), ssh_session(NULL), sftp_session(NULL) {
+#ifndef Q_WS_WIN
+ kDebug(KIO_SFTP_DB) << "pid = " << getpid();
+#endif
}
+sftpProtocol::~sftpProtocol() {
+#ifndef Q_WS_WIN
+ kDebug(KIO_SFTP_DB) << "pid = " << getpid();
+#endif
+ closeConnection();
-void sftpProtocol::openConnection() {
+ /* cleanup and shut down cryto stuff */
+ ssh_finalize();
+}
- if(mConnected)
- return;
+void sftpProtocol::setHost(const QString& h, quint16 port, const QString& user, const QString& pass) {
+ kDebug(KIO_SFTP_DB) << "setHost(): " << user << "@" << h << ":" << port;
- kDebug(KIO_SFTP_DB) << "username=" << mUsername << ", host=" << mHost << ", port=" << mPort;
+ if (mConnected) {
+ closeConnection();
+ }
- infoMessage( i18n("Opening SFTP connection to host %1:%2", mHost, mPort));
+ mHost = h;
- if( mHost.isEmpty() ) {
- kDebug(KIO_SFTP_DB) << "openConnection(): Need hostname...";
- error(ERR_UNKNOWN_HOST, i18n("No hostname specified"));
- return;
+ if (port > 0) {
+ mPort = port;
+ } else {
+ struct servent *pse;
+ if ((pse = getservbyname("ssh", "tcp") ) == NULL) {
+ mPort = 22;
+ } else {
+ mPort = ntohs(pse->s_port);
}
+ }
- // Setup AuthInfo for use with password caching and the
- // password dialog box.
- AuthInfo info;
- info.url.setProtocol("sftp");
- info.url.setHost(mHost);
- info.url.setPort(mPort);
- info.url.setUser(mUsername);
- info.caption = i18n("SFTP Login");
- info.comment = "sftp://" + mHost + ':' + QString::number(mPort);
- info.commentLabel = i18n("site:");
- info.username = mUsername;
- info.keepPassword = true;
+ kDebug(KIO_SFTP_DB) << "setHost(): mPort=" << mPort;
- // Check for cached authentication info if no password is specified...
- if( mPassword.isEmpty() ) {
- kDebug(KIO_SFTP_DB) << "checking cache: info.username = " << info.username
- << ", info.url = " << info.url.prettyUrl();
+ mUsername = user;
+ mPassword = pass;
- if( checkCachedAuthentication(info) ) {
- mUsername = info.username;
- mPassword = info.password;
- }
- }
+ if (user.isEmpty()) {
+ KUser u;
+ mUsername = u.loginName();
+ }
+}
- ///////////////////////////////////////////////////////////////////////////
- // Now setup our ssh options. If we found a cached username
- // and password we set the SSH_PASSWORD and SSH_USERNAME
- // options right away. Otherwise we wait. The other options are
- // necessary for running sftp over ssh.
- KSshProcess::SshOpt opt; // a ssh option, this can be reused
- KSshProcess::SshOptList opts; // list of SshOpts
- KSshProcess::SshOptListIterator passwdIt; // points to the opt in opts that specifies the password
- KSshProcess::SshOptListIterator usernameIt;
+void sftpProtocol::openConnection() {
-// opt.opt = KSshProcess::SSH_VERBOSE;
-// opts.append(opt);
-// opts.append(opt);
+ if (mConnected) {
+ return;
+ }
- // setHost will set a port in any case
- Q_ASSERT( mPort != -1 );
+ kDebug(KIO_SFTP_DB) << "username=" << mUsername << ", host=" << mHost << ", port=" << mPort;
- opt.opt = KSshProcess::SSH_PORT;
- opt.num = mPort;
- opts.append(opt);
+ infoMessage(i18n("Opening SFTP connection to host %1:%2", mHost, mPort));
- opt.opt = KSshProcess::SSH_SUBSYSTEM;
- opt.str = "sftp";
- opts.append(opt);
+ if (mHost.isEmpty()) {
+ kDebug(KIO_SFTP_DB) << "openConnection(): Need hostname...";
+ error(ERR_UNKNOWN_HOST, i18n("No hostname specified"));
+ return;
+ }
- opt.opt = KSshProcess::SSH_FORWARDX11;
- opt.boolean = false;
- opts.append(opt);
+ // Setup AuthInfo for use with password caching and the
+ // password dialog box.
+ AuthInfo info;
- opt.opt = KSshProcess::SSH_FORWARDAGENT;
- opt.boolean = false;
- opts.append(opt);
+ info.url.setProtocol("sftp");
+ info.url.setHost(mHost);
+ info.url.setPort(mPort);
+ info.url.setUser(mUsername);
+ info.caption = i18n("SFTP Login");
+ info.comment = "sftp://" + mHost + ':' + QString::number(mPort);
+ info.commentLabel = i18n("site:");
+ info.username = mUsername;
+ info.keepPassword = true;
- opt.opt = KSshProcess::SSH_PROTOCOL;
- opt.num = 2;
- opts.append(opt);
+ // Check for cached authentication info if no password is specified...
+ if (mPassword.isEmpty()) {
+ kDebug(KIO_SFTP_DB) << "checking cache: info.username = " << info.username
+ << ", info.url = " << info.url.prettyUrl();
- opt.opt = KSshProcess::SSH_HOST;
- opt.str = mHost;
- opts.append(opt);
-
- opt.opt = KSshProcess::SSH_ESCAPE_CHAR;
- opt.num = -1; // don't use any escape character
- opts.append(opt);
-
- // set the username and password if we have them
- if( !mUsername.isEmpty() ) {
- opt.opt = KSshProcess::SSH_USERNAME;
- opt.str = mUsername;
- opts.append(opt);
- usernameIt = opts.end()-1;
+ if (checkCachedAuthentication(info)) {
+ mUsername = info.username;
+ mPassword = info.password;
}
+ }
- if( !mPassword.isEmpty() ) {
- opt.opt = KSshProcess::SSH_PASSWD;
- opt.str = mPassword;
- opts.append(opt);
- passwdIt = opts.end()-1;
- }
+ // Create the libssh options
+ SSH_OPTIONS *opt = ssh_options_new();
+ if (opt == NULL) {
+ error(ERR_OUT_OF_MEMORY, QString());
+ return;
+ }
- ssh.setOptions(opts);
- ssh.printArgs();
+ kDebug(KIO_SFTP_DB) << "Creating the SSH options";
- ///////////////////////////////////////////////////////////////////////////
- // Start the ssh connection process.
- //
+ // Allow SSH1 only
+ ssh_options_allow_ssh1(opt, 0);
+ ssh_options_allow_ssh2(opt, 1);
- int err; // error code from KSshProcess
- QString msg; // msg for dialog box
- QString caption; // dialog box caption
- bool firstTime = true;
- bool dlgResult;
+ ssh_options_set_timeout(opt, 10, 0);
- while( !(mConnected = ssh.connect()) ) {
- err = ssh.error();
- kDebug(KIO_SFTP_DB) << "Got " << err << " from KSshProcess::connect()";
+ // Don't use any compression
+ ssh_options_set_wanted_algos(opt, SSH_COMP_C_S, "none");
+ ssh_options_set_wanted_algos(opt, SSH_COMP_S_C, "none");
- switch(err) {
- case KSshProcess::ERR_NEED_PASSWD:
- case KSshProcess::ERR_NEED_PASSPHRASE:
- // At this point we know that either we didn't set
- // an username or password in the ssh options list,
- // or what we did pass did not work. Therefore we
- // must prompt the user.
- if( err == KSshProcess::ERR_NEED_PASSPHRASE )
- info.prompt = i18n("Please enter your username and key passphrase.");
- else
- info.prompt = i18n("Please enter your username and password.");
+ // Set host and port
+ ssh_options_set_host(opt, mHost.toUtf8().constData());
+ if (mPort > 0) {
+ ssh_options_set_port(opt, mPort);
+ }
- kDebug(KIO_SFTP_DB) << "info.username = " << info.username
- << ", info.url = " << info.url.prettyUrl();
+ // Set the username
+ if (!mUsername.isEmpty()) {
+ ssh_options_set_username(opt, mUsername.toUtf8().constData());
+ }
- if( firstTime )
- dlgResult = openPasswordDialog(info);
- else
- dlgResult = openPasswordDialog(info, i18n("Incorrect username or password"));
+ ssh_options_set_auth_callback(opt, ::auth_callback, this);
- // handle user canceled or dialog failed to open...
- if( !dlgResult ) {
- error(ERR_USER_CANCELED, QString());
- kDebug(KIO_SFTP_DB) << "openConnection(): user canceled, dlgResult = " << dlgResult;
- closeConnection();
- return;
- }
+ // Start the ssh connection.
+ unsigned char *hash = NULL; // the server hash
+ char *hexa;
+ QString msg; // msg for dialog box
+ QString caption; // dialog box caption
+ int rc, state, hlen;
- firstTime = false;
+ ssh_session = ssh_new();
+ if (ssh_session == NULL) {
+ ssh_options_free(opt);
+ error(ERR_OUT_OF_MEMORY, QString());
+ return;
+ }
- // Check if the username has changed. SSH only accepts
- // the username at startup. If the username has changed
- // we must disconnect ssh, change the SSH_USERNAME
- // option, and reset the option list. We will also set
- // the password option so the user is not prompted for
- // it again.
- if( mUsername != info.username ) {
- kDebug(KIO_SFTP_DB) << "Username changed from " << mUsername
- << " to " << info.username;
+ kDebug(KIO_SFTP_DB) << "Creating the SSH session";
- ssh.disconnect();
+ ssh_set_options(ssh_session, opt);
- // if we haven't yet added the username
- // or password option to the ssh options list then
- // the iterators will be equal to the empty iterator.
- // Create the opts now and add them to the opt list.
- if( usernameIt == KSshProcess::SshOptListIterator() ) {
- kDebug(KIO_SFTP_DB) << "Adding username to options list";
- opt.opt = KSshProcess::SSH_USERNAME;
- opts.append(opt);
- usernameIt = opts.end()-1;
- }
+ kDebug(KIO_SFTP_DB) << "Trying to connect to the SSH server";
- if( passwdIt == KSshProcess::SshOptListIterator() ) {
- kDebug(KIO_SFTP_DB) << "Adding password to options list";
- opt.opt = KSshProcess::SSH_PASSWD;
- opts.append(opt);
- passwdIt = opts.end()-1;
- }
+ /* try to connect */
+ rc = ssh_connect(ssh_session);
+ if (rc < 0) {
+ error(ERR_COULD_NOT_CONNECT, QString(ssh_get_error(ssh_session)));
+ closeConnection();
+ return;
+ }
- (*usernameIt).str = info.username;
- (*passwdIt).str = info.password;
- ssh.setOptions(opts);
- ssh.printArgs();
- }
- else { // just set the password
- ssh.setPassword(info.password);
- }
+ kDebug(KIO_SFTP_DB) << "Getting the SSH server hash";
- mUsername = info.username;
- mPassword = info.password;
+ /* get the hash */
+ hlen = ssh_get_pubkey_hash(ssh_session, &hash);
+ if (hlen < 0) {
+ error(ERR_COULD_NOT_CONNECT, QString(ssh_get_error(ssh_session)));
+ closeConnection();
+ return;
+ }
- break;
+ kDebug(KIO_SFTP_DB) << "Checking if the SSH server is known";
- case KSshProcess::ERR_NEW_HOST_KEY:
- caption = i18n("Warning: Cannot verify host's identity.");
- msg = ssh.errorMsg();
- if( KMessageBox::Yes != messageBox(WarningYesNo, msg, caption) ) {
- closeConnection();
- error(ERR_USER_CANCELED, QString());
- return;
- }
- ssh.acceptHostKey(true);
- break;
+ /* check the server public key hash */
+ state = ssh_is_server_known(ssh_session);
+ switch (state) {
+ case SSH_SERVER_KNOWN_OK:
+ break;
+ case SSH_SERVER_FOUND_OTHER:
+ delete hash;
+ error(ERR_CONNECTION_BROKEN, i18n("The host key for this server was "
+ "not found but an other 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", ssh_get_error(ssh_session)));
+ closeConnection();
+ return;
+ case SSH_SERVER_KNOWN_CHANGED:
+ hexa = ssh_get_hexa(hash, hlen);
+ delete hash;
+ /* TODO print known_hosts file, port? */
+ error(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", mHost, hexa, ssh_get_error(ssh_session)));
+ delete hexa;
+ closeConnection();
+ return;
+ case SSH_SERVER_FILE_NOT_FOUND:
+ case SSH_SERVER_NOT_KNOWN:
+ hexa = ssh_get_hexa(hash, hlen);
+ delete hash;
+ caption = i18n("Warning: Cannot verify host's identity.");
+ msg = i18n("The authenticity of host %1 can't be established.\n"
+ "The key fingerprint is: %2\n"
+ "Are you sure you want to continue connecting?", mHost, hexa);
+ delete hexa;
- case KSshProcess::ERR_DIFF_HOST_KEY:
- caption = i18n("Warning: Host's identity changed.");
- msg = ssh.errorMsg();
- if( KMessageBox::Yes != messageBox(WarningYesNo, msg, caption) ) {
- closeConnection();
- error(ERR_USER_CANCELED, QString());
- return;
- }
- ssh.acceptHostKey(true);
- break;
-
- case KSshProcess::ERR_AUTH_FAILED:
- infoMessage(i18n("Authentication failed."));
- error(ERR_COULD_NOT_LOGIN, i18n("Authentication failed."));
- return;
-
- case KSshProcess::ERR_AUTH_FAILED_NEW_KEY:
- msg = ssh.errorMsg();
- error(ERR_COULD_NOT_LOGIN, msg);
- return;
-
- case KSshProcess::ERR_AUTH_FAILED_DIFF_KEY:
- msg = ssh.errorMsg();
- error(ERR_COULD_NOT_LOGIN, msg);
- return;
-
- case KSshProcess::ERR_CLOSED_BY_REMOTE_HOST:
- infoMessage(i18n("Connection failed."));
- caption = i18n("Connection closed by remote host.");
- msg = ssh.errorMsg();
- if (!msg.isEmpty()) {
- caption += '\n';
- caption += msg;
- }
- closeConnection();
- error(ERR_COULD_NOT_LOGIN, caption);
- return;
-
- case KSshProcess::ERR_INTERACT:
- case KSshProcess::ERR_INTERNAL:
- case KSshProcess::ERR_UNKNOWN:
- case KSshProcess::ERR_INVALID_STATE:
- case KSshProcess::ERR_CANNOT_LAUNCH:
- case KSshProcess::ERR_HOST_KEY_REJECTED:
- default:
- infoMessage(i18n("Connection failed."));
- // Don't call messageBox! Leave GUI handling to the apps (#108812)
- caption = i18n("unexpected SFTP error: %1", err);
- msg = ssh.errorMsg();
- if (!msg.isEmpty()) {
- caption += '\n';
- caption += msg;
- }
- closeConnection();
- error(ERR_UNKNOWN, caption);
- return;
- }
- }
-
- // catch all in case we did something wrong above
- if( !mConnected ) {
- error(ERR_INTERNAL, QString());
+ if (KMessageBox::Yes != messageBox(WarningYesNo, msg, caption)) {
+ closeConnection();
+ error(ERR_USER_CANCELED, QString());
return;
- }
+ }
- // Now send init packet.
- kDebug(KIO_SFTP_DB) << "openConnection(): Sending SSH2_FXP_INIT packet.";
- QByteArray p;
- QDataStream packet(&p, QIODevice::WriteOnly);
- packet << (quint32)5; // packet length
- packet << (quint8) SSH2_FXP_INIT; // packet type
- packet << (quint32)SSH2_FILEXFER_VERSION; // client version
-
- putPacket(p);
- getPacket(p);
-
- QDataStream s(p);
- quint32 version;
- quint8 type;
- s >> type;
- kDebug(KIO_SFTP_DB) << "openConnection(): Got type " << type;
-
- if( type == SSH2_FXP_VERSION ) {
- s >> version;
- kDebug(KIO_SFTP_DB) << "openConnection(): Got server version " << version;
-
- // XXX Get extensions here
- sftpVersion = version;
-
- /* Server should return lowest common version supported by
- * client and server, but double check just in case.
- */
- if( sftpVersion > SSH2_FILEXFER_VERSION ) {
- error(ERR_UNSUPPORTED_PROTOCOL,
- i18n("SFTP version %1", version));
- closeConnection();
- return;
- }
- }
- else {
- error(ERR_UNKNOWN, i18n("Protocol error."));
+ /* write the known_hosts file */
+ kDebug(KIO_SFTP_DB) << "Adding server to known_hosts file.";
+ if (ssh_write_knownhost(ssh_session) < 0) {
+ error(ERR_USER_CANCELED, QString(ssh_get_error(ssh_session)));
closeConnection();
return;
- }
+ }
+ break;
+ case SSH_SERVER_ERROR:
+ delete hash;
+ error(ERR_COULD_NOT_CONNECT, QString(ssh_get_error(ssh_session)));
+ return;
+ }
- // Login succeeded!
- infoMessage(i18n("Successfully connected to %1", mHost));
- info.url.setProtocol("sftp");
- info.url.setHost(mHost);
- info.url.setPort(mPort);
- info.url.setUser(mUsername);
- info.username = mUsername;
- info.password = mPassword;
- kDebug(KIO_SFTP_DB) << "Caching info.username = " << info.username
- << ", info.url = " << info.url.prettyUrl();
- cacheAuthentication(info);
- mConnected = true;
- connected();
+ kDebug(KIO_SFTP_DB) << "Trying to authenticate with the server";
- mPassword.fill('x');
- info.password.fill('x');
-
+ // Try to authenticate
+ rc = ssh_userauth_none(ssh_session, NULL);
+ if (rc == SSH_AUTH_ERROR) {
+ closeConnection();
+ error(ERR_COULD_NOT_LOGIN, i18n("Authentication failed."));
return;
-}
+ }
-#define _DEBUG kDebug(KIO_SFTP_DB)
+ int method = ssh_auth_list(ssh_session);
+ bool firstTime = true;
+ bool dlgResult;
+ while (rc != SSH_AUTH_SUCCESS) {
-void sftpProtocol::open(const KUrl &url, QIODevice::OpenMode mode)
-{
- _DEBUG << url;
- openConnection();
- if (!mConnected) {
- error(KIO::ERR_CONNECTION_BROKEN, url.prettyUrl());
+ // Try to authenticate with public key first
+ kDebug(KIO_SFTP_DB) << "Trying to authenticate public key";
+ if (method & SSH_AUTH_METHOD_PUBLICKEY) {
+ rc = ssh_userauth_autopubkey(ssh_session, NULL);
+ if (rc == SSH_AUTH_ERROR) {
+ closeConnection();
+ error(ERR_COULD_NOT_LOGIN, i18n("Authentication failed."));
return;
+ } else if (rc == SSH_AUTH_SUCCESS) {
+ break;
+ }
}
- int code;
- sftpFileAttr attr(remoteEncoding());
-
- if ((code = sftpStat(url, attr)) != SSH2_FX_OK) {
- _DEBUG << "stat error";
- processStatus(code, url.prettyUrl());
- return;
+ info.caption = i18n("SFTP Login");
+ info.prompt = i18n("Please enter your username and password.");
+ info.readOnly = false;
+ if (firstTime) {
+ dlgResult = openPasswordDialog(info);
+ } else {
+ dlgResult = openPasswordDialog(info, i18n("Incorrect username or password"));
}
- // don't open a directory
- if (attr.fileType() == S_IFDIR) {
- _DEBUG << "a directory";
- error(KIO::ERR_IS_DIRECTORY, url.prettyUrl());
- return;
+ // Handle user canceled or dialog failed to open...
+ if (!dlgResult) {
+ kDebug(KIO_SFTP_DB) << "User canceled, dlgResult = " << dlgResult;
+ closeConnection();
+ error(ERR_USER_CANCELED, QString());
+ return;
}
- if (attr.fileType() != S_IFREG) {
- _DEBUG << "not a regular file";
- error(KIO::ERR_CANNOT_OPEN_FOR_READING, url.prettyUrl());
- return;
- }
- KIO::filesize_t fileSize = attr.fileSize();
- attr.clear();
+ firstTime = false;
- quint32 pflags = 0;
-
- if (mode & QIODevice::ReadOnly) {
- if (mode & QIODevice::WriteOnly) {
- pflags = SSH2_FXF_READ | SSH2_FXF_WRITE | SSH2_FXF_CREAT;
- } else {
- pflags = SSH2_FXF_READ;
- }
- } else if (mode & QIODevice::WriteOnly) {
- pflags = SSH2_FXF_WRITE | SSH2_FXF_CREAT;
+ if (mUsername != info.username) {
+ kDebug(KIO_SFTP_DB) << "Username changed from " << mUsername
+ << " to " << info.username;
}
+ mUsername = info.username;
+ mPassword = info.password;
- if (mode & QIODevice::Append) {
- pflags |= SSH2_FXF_APPEND;
- } else if (mode & QIODevice::Truncate) {
- pflags |= SSH2_FXF_TRUNC;
+ // Try to authenticate with keyboard interactive
+ kDebug(KIO_SFTP_DB) << "Trying to authenticate with keyboard interactive";
+ if (method & SSH_AUTH_METHOD_INTERACTIVE) {
+ rc = authenticateKeyboardInteractive(info);
+ if (rc == SSH_AUTH_ERROR) {
+ closeConnection();
+ error(ERR_COULD_NOT_LOGIN, i18n("Authentication failed."));
+ return;
+ } else if (rc == SSH_AUTH_SUCCESS) {
+ break;
+ }
}
- code = sftpOpen(url, pflags, attr, openHandle);
- if (code != SSH2_FX_OK) {
- _DEBUG << "Got error code " << code;
- processStatus(code, url.prettyUrl());
+ // Try to authenticate with password
+ kDebug(KIO_SFTP_DB) << "Trying to authenticate with password";
+ if (method & SSH_AUTH_METHOD_PASSWORD) {
+ rc = ssh_userauth_password(ssh_session, mUsername.toUtf8().constData(),
+ mPassword.toUtf8().constData());
+ if (rc == SSH_AUTH_ERROR) {
+ closeConnection();
+ error(ERR_COULD_NOT_LOGIN, i18n("Authentication failed."));
return;
+ } else if (rc == SSH_AUTH_SUCCESS) {
+ break;
+ }
}
+ }
- // 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.
- if (mode & QIODevice::ReadOnly){
- QByteArray buffer;
- code = sftpRead(openHandle, 0, 1024, buffer);
- if ((code != SSH2_FX_OK) && (code != SSH2_FX_EOF)){
- _DEBUG << "error on mime type detection";
- processStatus(code, url.prettyUrl());
- close();
- return;
- }
- KMimeType::Ptr mime = KMimeType::findByNameAndContent(url.fileName(), buffer);
- mimeType(mime->name());
- }
+ // start sftp session
+ kDebug(KIO_SFTP_DB) << "Trying to request the sftp session";
+ sftp_session = sftp_new(ssh_session);
+ if (sftp_session == NULL) {
+ closeConnection();
+ error(ERR_COULD_NOT_LOGIN, i18n("Unable to request the SFTP subsystem. "
+ "Make sure SFTP is enabled on the server"));
+ return;
+ }
- openUrl = url;
- openOffset = 0;
- totalSize(fileSize);
- position(0);
- opened();
-}
+ kDebug(KIO_SFTP_DB) << "Trying to initialize the sftp session";
+ if (sftp_init(sftp_session) < 0) {
+ closeConnection();
+ error(ERR_COULD_NOT_LOGIN, i18n("Could not initialize the SFTP session."));
+ return;
+ }
-void sftpProtocol::read(KIO::filesize_t bytes)
-{
- _DEBUG << "read, offset = " << openOffset << ", bytes = " << bytes;
- QByteArray buffer;
- int code = sftpRead(openHandle, openOffset, bytes, buffer);
- if ((code == SSH2_FX_OK) || (code == SSH2_FX_EOF)) {
- openOffset += buffer.size();
- data(buffer);
- } else {
- processStatus(code, openUrl.prettyUrl());
- close();
- }
-}
+ // Login succeeded!
+ infoMessage(i18n("Successfully connected to %1", mHost));
+ info.url.setProtocol("sftp");
+ info.url.setHost(mHost);
+ info.url.setPort(mPort);
+ info.url.setUser(mUsername);
+ info.username = mUsername;
+ info.password = mPassword;
-void sftpProtocol::write(const QByteArray &data)
-{
- _DEBUG << "write";
- int code = sftpWrite(openHandle, openOffset, data);
- if (code == SSH2_FX_OK) {
- openOffset += data.size();
- written(data.size());
- } else {
- processStatus(code, openUrl.prettyUrl());
- close();
- }
-}
+ kDebug(KIO_SFTP_DB) << "Caching info.username = " << info.username
+ << ", info.url = " << info.url.prettyUrl();
-void sftpProtocol::seek(KIO::filesize_t offset)
-{
- _DEBUG << "seek, offset = " << offset;
- openOffset = offset;
- position(offset);
-}
+ cacheAuthentication(info);
-void sftpProtocol::close()
-{
- sftpClose(openHandle);
- _DEBUG << "emitting finished";
- finished();
+ mConnected = true;
+ connected();
+
+ mPassword.fill('x');
+ mPassword.clear();
+ info.password.fill('x');
+ info.password.clear();
+
+ return;
}
-#undef _DEBUG
void sftpProtocol::closeConnection() {
- kDebug(KIO_SFTP_DB) << "closeConnection()";
- ssh.disconnect();
- mConnected = false;
-}
+ kDebug(KIO_SFTP_DB) << "closeConnection()";
-void sftpProtocol::sftpCopyPut(const KUrl& src, const KUrl& dest, int permissions, KIO::JobFlags flags) {
- KDE_struct_stat buff;
- QByteArray file (QFile::encodeName(src.path()));
+ sftp_free(sftp_session);
+ sftp_session = NULL;
- if (KDE_lstat(file.data(), &buff) == -1) {
- error (ERR_DOES_NOT_EXIST, src.prettyUrl());
- return;
- }
+ ssh_disconnect(ssh_session);
+ ssh_session = NULL;
- if (S_ISDIR (buff.st_mode)) {
- error (ERR_IS_DIRECTORY, src.prettyUrl());
- return;
- }
-
- int fd = KDE_open (file.data(), O_RDONLY);
- if (fd == -1) {
- error (ERR_CANNOT_OPEN_FOR_READING, src.prettyUrl());
- return;
- }
-
- totalSize (buff.st_size);
-
- sftpPut (dest, permissions, (flags & ~KIO::Resume), fd);
-
- // Close the file descriptor...
- ::close( fd );
+ mConnected = false;
}
-void sftpProtocol::sftpPut( const KUrl& dest, int permissions, KIO::JobFlags flags, int fd ) {
+void sftpProtocol::open(const KUrl &url, QIODevice::OpenMode mode) {
+ kDebug(KIO_SFTP_DB) << "open: " << url;
- openConnection();
- if( !mConnected )
- return;
+ openConnection();
+ if (!mConnected) {
+ error(KIO::ERR_CONNECTION_BROKEN, url.prettyUrl());
+ return;
+ }
- kDebug(KIO_SFTP_DB) << dest << ", resume=" << (flags & KIO::Resume) << ", overwrite=" << (flags & KIO::Overwrite);
+ const QString path = url.path();
+ const QByteArray path_c = path.toUtf8();
- KUrl origUrl( dest );
- sftpFileAttr origAttr(remoteEncoding());
- bool origExists = false;
+ SFTP_ATTRIBUTES *sb = sftp_lstat(sftp_session, path_c.constData());
+ if (sb == NULL) {
+ reportError(url, sftp_get_error(sftp_session));
+ return;
+ }
- // Stat original (without part ext) to see if it already exists
- int code = sftpStat(origUrl, origAttr);
+ switch (sb->type) {
+ case SSH_FILEXFER_TYPE_DIRECTORY:
+ error(KIO::ERR_IS_DIRECTORY, url.prettyUrl());
+ sftp_attributes_free(sb);
+ return;
+ case SSH_FILEXFER_TYPE_SPECIAL:
+ case SSH_FILEXFER_TYPE_UNKNOWN:
+ error(KIO::ERR_CANNOT_OPEN_FOR_READING, url.prettyUrl());
+ sftp_attributes_free(sb);
+ return;
+ case SSH_FILEXFER_TYPE_SYMLINK:
+ case SSH_FILEXFER_TYPE_REGULAR:
+ break;
+ }
- if( code == SSH2_FX_OK ) {
- kDebug(KIO_SFTP_DB) << qPrintable(origUrl.url()) << " already exists!";
+ KIO::filesize_t fileSize = sb->size;
+ sftp_attributes_free(sb);
- // Delete remote file if its size is zero
- if( origAttr.fileSize() == 0 ) {
- if( sftpRemove(origUrl, true) != SSH2_FX_OK ) {
- error(ERR_CANNOT_DELETE_ORIGINAL, origUrl.prettyUrl());
- return;
- }
- }
- else {
- origExists = true;
- }
- }
- else if( code != SSH2_FX_NO_SUCH_FILE ) {
- processStatus(code, origUrl.prettyUrl());
- return;
- }
+ int flags = 0;
- // Do not waste time/resources with more remote stat calls if the file exists
- // and we weren't instructed to overwrite it...
- if( origExists && !(flags & KIO::Overwrite) ) {
- error(ERR_FILE_ALREADY_EXIST, origUrl.prettyUrl());
- return;
+ if (mode & QIODevice::ReadOnly) {
+ if (mode & QIODevice::WriteOnly) {
+ flags = O_RDWR | O_CREAT;
+ } else {
+ flags = O_RDONLY;
}
+ } else if (mode & QIODevice::WriteOnly) {
+ flags = O_WRONLY | O_CREAT;
+ }
- // Stat file with part ext to see if it already exists...
- KUrl partUrl( origUrl );
- partUrl.setFileName( partUrl.fileName() + ".part" );
+ if (mode & QIODevice::Append) {
+ flags |= O_APPEND;
+ } else if (mode & QIODevice::Truncate) {
+ flags |= O_TRUNC;
+ }
- quint64 offset = 0;
- bool partExists = false;
- bool markPartial = config()->readEntry("MarkPartial", true);
+ if (flags & O_CREAT) {
+ mOpenFile = sftp_open(sftp_session, path_c.constData(), flags, 0666);
+ } else {
+ mOpenFile = sftp_open(sftp_session, path_c.constData(), flags, 0);
+ }
- if( markPartial ) {
+ if (mOpenFile == NULL) {
+ error(KIO::ERR_CANNOT_OPEN_FOR_READING, path);
+ return;
+ }
- sftpFileAttr partAttr(remoteEncoding());
- code = sftpStat(partUrl, partAttr);
+ // 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.
+ if (mode & QIODevice::ReadOnly) {
+ size_t bytesRequested = 1024;
+ ssize_t bytesRead = 0;
+ QVarLengthArray<char> buffer(bytesRequested);
- if( code == SSH2_FX_OK ) {
- kDebug(KIO_SFTP_DB) << ".part file already exists";
- partExists = true;
- offset = partAttr.fileSize();
+ bytesRead = sftp_read(mOpenFile, buffer.data(), bytesRequested);
+ if (bytesRead < 0) {
+ error(KIO::ERR_COULD_NOT_READ, mOpenUrl.prettyUrl());
+ close();
+ return;
+ } else {
+ QByteArray fileData = QByteArray::fromRawData(buffer.data(), bytesRead);
+ KMimeType::Ptr p_mimeType = KMimeType::findByNameAndContent(mOpenUrl.fileName(), fileData);
+ emit mimeType(p_mimeType->name());
- // If for some reason, both the original and partial files exist,
- // skip resumption just like we would if the size of the partial
- // file is zero...
- if( origExists || offset == 0 )
- {
- if( sftpRemove(partUrl, true) != SSH2_FX_OK ) {
- error(ERR_CANNOT_DELETE_PARTIAL, partUrl.prettyUrl());
- return;
- }
-
- if( sftpRename(origUrl, partUrl) != SSH2_FX_OK ) {
- error(ERR_CANNOT_RENAME_ORIGINAL, origUrl.prettyUrl());
- return;
- }
-
- offset = 0;
- }
- else if( !(flags & KIO::Overwrite) && !(flags & KIO::Resume) ) {
- if (fd != -1)
- flags |= (KDE_lseek(fd, offset, SEEK_SET) != -1) ? KIO::Resume : KIO::DefaultFlags;
- else
- flags |= canResume( offset ) ? KIO::Resume : KIO::DefaultFlags;
-
- kDebug(KIO_SFTP_DB) << "can resume = " << (flags & KIO::Resume)
- << ", offset = " << offset;
-
- if( !(flags & KIO::Resume) ) {
- error(ERR_FILE_ALREADY_EXIST, partUrl.prettyUrl());
- return;
- }
- }
- else {
- offset = 0;
- }
- }
- else if( code == SSH2_FX_NO_SUCH_FILE ) {
- if( origExists && sftpRename(origUrl, partUrl) != SSH2_FX_OK ) {
- error(ERR_CANNOT_RENAME_ORIGINAL, origUrl.prettyUrl());
- return;
- }
- }
- else {
- processStatus(code, partUrl.prettyUrl());
- return;
- }
+ // Go back to the beginning of the file.
+ sftp_rewind(mOpenFile);
}
+ }
- // Determine the url we will actually write to...
- KUrl writeUrl (markPartial ? partUrl:origUrl);
+ mOpenUrl = url;
- quint32 pflags = 0;
- if( (flags & KIO::Overwrite) && !(flags & KIO::Resume) )
- pflags = SSH2_FXF_WRITE | SSH2_FXF_CREAT | SSH2_FXF_TRUNC;
- else if( !(flags & KIO::Overwrite) && !(flags & KIO::Resume) )
- pflags = SSH2_FXF_WRITE | SSH2_FXF_CREAT | SSH2_FXF_EXCL;
- else if( (flags & KIO::Overwrite) && (flags & KIO::Resume) )
- pflags = SSH2_FXF_WRITE | SSH2_FXF_CREAT;
- else if( !(flags & KIO::Overwrite) && (flags & KIO::Resume) )
- pflags = SSH2_FXF_WRITE | SSH2_FXF_CREAT | SSH2_FXF_APPEND;
+ openOffset = 0;
+ totalSize(fileSize);
+ position(0);
+ opened();
+}
- sftpFileAttr attr(remoteEncoding());
- QByteArray handle;
+void sftpProtocol::read(KIO::filesize_t bytes) {
+ kDebug(KIO_SFTP_DB) << "read, offset = " << openOffset << ", bytes = " << bytes;
- // Set the permissions of the file we write to if it didn't already exist
- // and the permission info is supplied, i.e it is not -1
- if( !partExists && !origExists && permissions != -1)
- attr.setPermissions(permissions);
+ Q_ASSERT(mOpenFile != NULL);
- code = sftpOpen( writeUrl, pflags, attr, handle );
- if( code != SSH2_FX_OK ) {
+ QVarLengthArray<char> buffer(bytes);
- // Rename the file back to its original name if a
- // put fails due to permissions problems...
- if( markPartial && (flags & KIO::Overwrite) ) {
- (void) sftpRename(partUrl, origUrl);
- writeUrl = origUrl;
- }
+ ssize_t bytesRead = sftp_read(mOpenFile, buffer.data(), bytes);
+ Q_ASSERT(bytesRead <= static_cast<ssize_t>(bytes));
- if( code == SSH2_FX_FAILURE ) { // assume failure means file exists
- error(ERR_FILE_ALREADY_EXIST, writeUrl.prettyUrl());
- return;
- }
- else {
- processStatus(code, writeUrl.prettyUrl());
- return;
- }
- }
+ if (bytesRead < 0) {
+ kDebug(KIO_SFTP_DB) << "Could not read " << mOpenUrl;
+ error(KIO::ERR_COULD_NOT_READ, mOpenUrl.prettyUrl());
+ close();
+ return;
+ }
- long nbytes;
- QByteArray buff;
+ QByteArray fileData = QByteArray::fromRawData(buffer.data(), bytesRead);
+ data(fileData);
+}
- do {
+void sftpProtocol::write(const QByteArray &data) {
+ kDebug(KIO_SFTP_DB) << "write, offset = " << openOffset << ", bytes = " << data.size();
- if( fd != -1 ) {
- buff.resize( 16*1024 );
- if ( (nbytes = ::read(fd, buff.data(), buff.size())) > -1 )
- buff.resize( nbytes );
- }
- else {
- dataReq();
- nbytes = readData( buff );
- }
+ Q_ASSERT(mOpenFile != NULL);
- if( nbytes > 0 ) {
- if( (code = sftpWrite(handle, offset, buff)) != SSH2_FX_OK ) {
- error(ERR_COULD_NOT_WRITE, dest.prettyUrl());
- return;
- }
+ ssize_t bytesWritten = sftp_write(mOpenFile, data.data(), data.size());
+ if (bytesWritten < 0) {
+ kDebug(KIO_SFTP_DB) << "Could not write to " << mOpenUrl;
+ error(KIO::ERR_COULD_NOT_WRITE, mOpenUrl.prettyUrl());
+ close();
+ return;
+ }
- offset += nbytes;
- processedSize(offset);
+ written(bytesWritten);
+}
- /* Check if slave was killed. According to slavebase.h we
- * need to leave the slave methods as soon as possible if
- * the slave is killed. This allows the slave to be cleaned
- * up properly.
- */
- if( wasKilled() ) {
- sftpClose(handle);
- closeConnection();
- error(ERR_UNKNOWN, i18n("An internal error occurred. Please try again."));
- return;
- }
- }
+void sftpProtocol::seek(KIO::filesize_t offset) {
+ kDebug(KIO_SFTP_DB) << "seek, offset = " << offset;
- } while( nbytes > 0 );
+ Q_ASSERT(mOpenFile != NULL);
- if( nbytes < 0 ) {
- sftpClose(handle);
+ if (sftp_seek64(mOpenFile, static_cast<uint64_t>(offset)) < 0) {
+ error(KIO::ERR_COULD_NOT_SEEK, mOpenUrl.path());
+ close();
+ }
- if( markPartial ) {
- // Remove remote file if it smaller than our keep size
- uint minKeepSize = config()->readEntry("MinimumKeepSize", DEFAULT_MINIMUM_KEEP_SIZE);
-
- if( sftpStat(writeUrl, attr) == SSH2_FX_OK ) {
- if( attr.fileSize() < minKeepSize ) {
- sftpRemove(writeUrl, true);
- }
- }
- }
-
- error( ERR_UNKNOWN, i18n("Unknown error was encountered while copying the file "
- "to '%1'. Please try again.", dest.host()) );
- return;
- }
-
- if( (code = sftpClose(handle)) != SSH2_FX_OK ) {
- error(ERR_COULD_NOT_WRITE, writeUrl.prettyUrl());
- return;
- }
-
- // If wrote to a partial file, then remove the part ext
- if( markPartial ) {
- if( sftpRename(partUrl, origUrl) != SSH2_FX_OK ) {
- error(ERR_CANNOT_RENAME_PARTIAL, origUrl.prettyUrl());
- return;
- }
- }
-
- finished();
+ position(sftp_tell64(mOpenFile));
}
-void sftpProtocol::put ( const KUrl& url, int permissions, KIO::JobFlags flags ) {
- kDebug(KIO_SFTP_DB) << "put(): " << url << ", overwrite = " << (flags & KIO::Overwrite)
- << ", resume = " << (flags & KIO::Resume);
+void sftpProtocol::close() {
+ sftp_close(mOpenFile);
- sftpPut( url, permissions, flags );
+ mOpenFile = NULL;
+ finished();
}
-void sftpProtocol::stat ( const KUrl& url ){
- kDebug(KIO_SFTP_DB) << url;
+void sftpProtocol::get(const KUrl& url) {
+ kDebug(KIO_SFTP_DB) << "get(): " << url;
- openConnection();
- if( !mConnected )
- return;
+ openConnection();
+ if (!mConnected) {
+ return;
+ }
- // If the stat URL has no path, do not attempt to determine the real
- // path and do a redirect. KRun will simply ignore such requests.
- // Instead, simply return the mime-type as a directory...
- if( !url.hasPath() ) {
- UDSEntry entry;
+ QByteArray path = url.path().toUtf8();
- entry.insert( KIO::UDSEntry::UDS_NAME, QString::fromLatin1(".") );
- entry.insert( KIO::UDSEntry::UDS_FILE_TYPE, S_IFDIR );
- entry.insert( KIO::UDSEntry::UDS_ACCESS, S_IRUSR | S_IXUSR | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH );
- entry.insert( KIO::UDSEntry::UDS_USER, mUsername );
- entry.insert( KIO::UDSEntry::UDS_GROUP, mUsername );
+ 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;
+ KIO::filesize_t totalbytesread = 0;
+ QByteArray filedata;
- // no size
- statEntry( entry );
- finished();
- return;
- }
+ SFTP_ATTRIBUTES *sb = sftp_lstat(sftp_session, path.constData());
+ if (sb == NULL) {
+ reportError(url, sftp_get_error(sftp_session));
+ return;
+ }
- int code;
- sftpFileAttr attr(remoteEncoding());
- if( (code = sftpStat(url, attr)) != SSH2_FX_OK ) {
- processStatus(code, url.prettyUrl());
- return;
- }
- else {
- //kDebug() << "We sent and received stat packet ok";
- //attr.setFilename(url.fileName());
- statEntry(attr.entry());
- }
+ if (sb->type == SSH_FILEXFER_TYPE_DIRECTORY) {
+ error(KIO::ERR_IS_DIRECTORY, url.prettyUrl());
+ sftp_attributes_free(sb);
+ return;
+ }
+ if (sb->type != SSH_FILEXFER_TYPE_REGULAR) {
+ error(KIO::ERR_CANNOT_OPEN_FOR_READING, url.prettyUrl());
+ sftp_attributes_free(sb);
+ return;
+ }
- finished();
-
- //kDebug(KIO_SFTP_DB) << "END";
+ // Open file
+ file = sftp_open(sftp_session, path.constData(), O_RDONLY, 0);
+ if (file == NULL) {
+ error( KIO::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
-void sftpProtocol::mimetype ( const KUrl& url ){
- kDebug(KIO_SFTP_DB) << url;
+ // Set the total size
+ totalSize(sb->size);
- openConnection();
- if( !mConnected )
- return;
-
- Status info = sftpGet(url, 0 /*offset*/, -1, true /*only emit mimetype*/);
-
- if (info.code != 0)
+ const QString resumeOffset = metaData(QLatin1String("resume"));
+ if (!resumeOffset.isEmpty()) {
+ bool ok;
+ KIO::fileoffset_t offset = resumeOffset.toLongLong(&ok);
+ if (ok && (offset > 0) && ((unsigned long long) offset < sb->size))
{
- error(info.code, info.text);
- return;
+ if (sftp_seek64(file, offset) == 0) {
+ canResume();
+ totalbytesread = offset;
+ kDebug(KIO_SFTP_DB) << "Resume offset: " << QString::number(offset);
+ }
}
+ }
- finished();
-}
+ if (file != NULL) {
+ bool isFirstPacket = true;
+ lasttime = starttime = time(NULL);
-
-void sftpProtocol::listDir(const KUrl& url) {
- kDebug(KIO_SFTP_DB) << url;
-
- openConnection();
- if( !mConnected )
+ for (;;) {
+ bytesread = sftp_read(file, buf, MAX_XFER_BUF_SIZE);
+ kDebug(KIO_SFTP_DB) << "bytesread=" << QString::number(bytesread);
+ if (bytesread == 0) {
+ // All done reading
+ break;
+ } else if (bytesread < 0) {
+ error( KIO::ERR_COULD_NOT_READ, url.prettyUrl());
+ sftp_attributes_free(sb);
return;
+ }
- if( !url.hasPath() ) {
- KUrl newUrl ( url );
- if( sftpRealPath(url, newUrl) == SSH2_FX_OK ) {
- kDebug(KIO_SFTP_DB) << "listDir: Redirecting to " << newUrl;
- redirection(newUrl);
- finished();
- return;
- }
- }
+ filedata = QByteArray::fromRawData(buf, bytesread);
+ if (isFirstPacket) {
+ KMimeType::Ptr p_mimeType = KMimeType::findByNameAndContent(url.fileName(), filedata);
+ mimeType(p_mimeType->name());
+ isFirstPacket = false;
+ }
+ data(filedata);
+ filedata.clear();
- int code;
- QByteArray handle;
+ // increment total bytes read
+ totalbytesread += bytesread;
- if( (code = sftpOpenDirectory(url, handle)) != SSH2_FX_OK ) {
- kError(KIO_SFTP_DB) << "listDir(): open directory failed";
- processStatus(code, url.prettyUrl());
- return;
+ processedSize(totalbytesread);
}
+ sftp_close(file);
+ data(QByteArray());
+ processedSize(static_cast<KIO::filesize_t>(sb->size));
+ }
- code = SSH2_FX_OK;
- while( code == SSH2_FX_OK ) {
- code = sftpReadDir(handle, url);
- if( code != SSH2_FX_OK && code != SSH2_FX_EOF )
- processStatus(code, url.prettyUrl());
- kDebug(KIO_SFTP_DB) << "listDir(): return code = " << code;
- }
-
- if( (code = sftpClose(handle)) != SSH2_FX_OK ) {
- kError(KIO_SFTP_DB) << "listdir(): closing of directory failed";
- processStatus(code, url.prettyUrl());
- return;
- }
-
- finished();
- kDebug(KIO_SFTP_DB) << "listDir(): END";
+ sftp_attributes_free(sb);
+ finished();
}
-/** Make a directory.
- OpenSSH does not follow the internet draft for sftp in this case.
- The format of the mkdir request expected by OpenSSH sftp server is:
- uint32 id
- string path
- ATTR attr
- */
-void sftpProtocol::mkdir(const KUrl&url, int permissions){
+void sftpProtocol::put(const KUrl& url, int permissions, KIO::JobFlags flags) {
+ kDebug(KIO_SFTP_DB) << "put(): " << url
+ << " , permissions = " << QString::number(permissions)
+ << ", overwrite = " << (flags & KIO::Overwrite)
+ << ", resume = " << (flags & KIO::Resume);
- kDebug(KIO_SFTP_DB) << "create directory: " << url.path();
+ openConnection();
+ if (!mConnected) {
+ return;
+ }
- openConnection();
- if( !mConnected )
- return;
+ const QString dest_orig = url.path();
+ const QByteArray dest_orig_c = dest_orig.toUtf8();
+ const QString dest_part = dest_orig + ".part";
+ const QByteArray dest_part_c = dest_part.toUtf8();
- QByteArray path = remoteEncoding()->encode(url.path());
- uint len = path.length();
+ SFTP_ATTRIBUTES *sb = sftp_lstat(sftp_session, dest_orig_c.constData());
+ const bool bOrigExists = (sb != NULL);
+ bool bPartExists = false;
+ const bool bMarkPartial = config()->readEntry("MarkPartial", true);
- sftpFileAttr attr(remoteEncoding());
+ if (bMarkPartial) {
+ SFTP_ATTRIBUTES *sbPart = sftp_lstat(sftp_session, dest_part_c.constData());
+ bPartExists = (sbPart != NULL);
- if (permissions != -1)
- attr.setPermissions(permissions);
+ if (bPartExists && !(flags & KIO::Resume) && !(flags & KIO::Overwrite) &&
+ sbPart->size > 0 && sbPart->type == SSH_FILEXFER_TYPE_REGULAR) {
+ kDebug(KIO_SFTP_DB) << "put : calling canResume with "
+ << QString::number(sbPart->size);
- quint32 id, expectedId;
- id = expectedId = mMsgId++;
+ // 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.
+ flags |= canResume(sbPart->size) ? KIO::Resume : KIO::DefaultFlags;
- QByteArray p;
- QDataStream s(&p, QIODevice::WriteOnly);
- s << quint32(1 /*type*/ + 4 /*id*/ + 4 /*str length*/ + len + attr.size());
- s << (quint8)SSH2_FXP_MKDIR;
- s << id;
- s.writeBytes(path.data(), len);
- s << attr;
+ kDebug(KIO_SFTP_DB) << "put got answer " << (flags & KIO::Resume);
- kDebug(KIO_SFTP_DB) << "mkdir(): packet size is " << p.size();
-
- putPacket(p);
- getPacket(p);
-
- quint8 type;
- QDataStream r(p);
-
- r >> type >> id;
- if( id != expectedId ) {
- kError(KIO_SFTP_DB) << "mkdir: sftp packet id mismatch";
- error(ERR_COULD_NOT_MKDIR, url.path());
- finished();
- return;
+ delete sbPart;
}
+ }
- if( type != SSH2_FXP_STATUS ) {
- kError(KIO_SFTP_DB) << "mkdir(): unexpected packet type of" << type;
- error(ERR_COULD_NOT_MKDIR, url.path());
- finished();
- return;
+ if (bOrigExists && !(flags & KIO::Overwrite) && !(flags & KIO::Resume)) {
+ if (sb->type == SSH_FILEXFER_TYPE_DIRECTORY) {
+ error(KIO::ERR_DIR_ALREADY_EXIST, dest_orig);
+ } else {
+ error(KIO::ERR_FILE_ALREADY_EXIST, dest_orig);
}
+ sftp_attributes_free(sb);
+ return;
+ }
- int code;
- r >> code;
- if( code != SSH2_FX_OK ) {
- kError(KIO_SFTP_DB) << "mkdir(): failed with code " << code;
+ int result;
+ QByteArray dest;
+ SFTP_FILE *file = NULL;
- // Check if mkdir failed because the directory already exists so that
- // we can return the appropriate message...
- sftpFileAttr dirAttr(remoteEncoding());
- if ( sftpStat(url, dirAttr) == SSH2_FX_OK )
- {
- error( ERR_DIR_ALREADY_EXIST, url.prettyUrl() );
- return;
+ // Loop until we got 0 (end of data)
+ do {
+ QByteArray buffer;
+ dataReq(); // Request for data
+ result = readData(buffer);
+
+ if (result >= 0) {
+ if (dest.isEmpty()) {
+ if (bMarkPartial) {
+ kDebug(KIO_SFTP_DB) << "Appending .part extension to " << dest_orig;
+ dest = dest_part_c;
+ if (bPartExists && !(flags & KIO::Resume)) {
+ kDebug(KIO_SFTP_DB) << "Deleting partial file " << dest_part;
+ sftp_unlink(sftp_session, dest_part_c.constData());
+ // Catch errors when we try to open the file.
+ }
+ } else {
+ dest = dest_orig_c;
+ if (bOrigExists && !(flags & KIO::Resume)) {
+ kDebug(KIO_SFTP_DB) << "Deleting destination file " << dest_orig;
+ sftp_unlink(sftp_session, dest_orig_c.constData());
+ // Catch errors when we try to open the file.
+ }
}
- error(ERR_COULD_NOT_MKDIR, url.path());
- }
+ if ((flags & KIO::Resume)) {
+ file = sftp_open(sftp_session, dest.constData(), O_RDWR, 0); // append if resuming
+ SFTP_ATTRIBUTES *fstat = sftp_fstat(file);
+ if (fstat == NULL) {
+ reportError(url, sftp_get_error(sftp_session));
+ sftp_attributes_free(sb);
+ return;
+ }
+ 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 = 0666;
+ }
- finished();
-}
-
-void sftpProtocol::rename(const KUrl& src, const KUrl& dest, KIO::JobFlags flags) {
- kDebug(KIO_SFTP_DB) << src << " -> " << dest;
-
- if (!isSupportedOperation(SSH2_FXP_RENAME)) {
- error(ERR_UNSUPPORTED_ACTION,
- i18n("The remote host does not support renaming files."));
- return;
- }
-
- openConnection();
- if( !mConnected )
- return;
-
- // Always stat the destination before attempting to rename
- // a file or a directory...
- sftpFileAttr attr(remoteEncoding());
- int code = sftpStat(dest, attr);
-
- // If the destination directory, exists tell it to the job
- // so it the proper action can be presented to the user...
- if( code == SSH2_FX_OK )
- {
- if (!(flags & KIO::Overwrite))
- {
- if ( S_ISDIR(attr.permissions()) )
- error( KIO::ERR_DIR_ALREADY_EXIST, dest.url() );
- else
- error( KIO::ERR_FILE_ALREADY_EXIST, dest.url() );
- return;
+ kDebug(KIO_SFTP_DB) << "Trying to open: " << dest << ", mode=" << QString::number(initialMode);
+ file = sftp_open(sftp_session, dest.constData(), O_CREAT | O_TRUNC | O_WRONLY, initialMode);
}
- // If overwrite is specified, then simply remove the existing file/dir first...
- if( (code = sftpRemove( dest, !S_ISDIR(attr.permissions()) )) != SSH2_FX_OK )
- {
- processStatus(code);
- return;
+ if (file == NULL) {
+ kDebug(KIO_SFTP_DB) << "####################### COULD NOT WRITE " << dest << " permissions=" << permissions;
+ if (sftp_get_error(sftp_session) == SSH_FX_PERMISSION_DENIED) {
+ error(KIO::ERR_WRITE_ACCESS_DENIED, QString::fromUtf8(dest));
+ } else {
+ error(KIO::ERR_CANNOT_OPEN_FOR_WRITING, QString::fromUtf8(dest));
+ }
+ sftp_attributes_free(sb);
+ return;
}
- }
+ }
- // Do the renaming...
- if( (code = sftpRename(src, dest)) != SSH2_FX_OK ) {
- processStatus(code);
- return;
+ ssize_t bytesWritten = sftp_write(file, buffer.data(), buffer.size());
+ if (bytesWritten < 0) {
+ error(KIO::ERR_COULD_NOT_WRITE, dest_orig);
+ result = -1;
+ }
}
+ } while (result > 0);
+ sftp_attributes_free(sb);
- finished();
- kDebug(KIO_SFTP_DB) << "END";
-}
+ // An error occurred deal with it.
+ if (result < 0) {
+ kDebug(KIO_SFTP_DB) << "Error during 'put'. Aborting.";
-void sftpProtocol::symlink(const QString& target, const KUrl& dest, KIO::JobFlags flags) {
- kDebug(KIO_SFTP_DB) << "link " << target << "->" << dest;
+ if (file != NULL) {
+ sftp_close(file);
- if (!isSupportedOperation(SSH2_FXP_SYMLINK)) {
- error(ERR_UNSUPPORTED_ACTION,
- i18n("The remote host does not support creating symbolic links."));
- return;
- }
-
- openConnection();
- if( !mConnected )
- return;
-
- int code;
- bool failed = false;
- if( (code = sftpSymLink(target, dest)) != SSH2_FX_OK ) {
- if( flags & KIO::Overwrite ) { // try to delete the destination
- sftpFileAttr attr(remoteEncoding());
- if( (code = sftpStat(dest, attr)) != SSH2_FX_OK ) {
- failed = true;
- }
- else {
- if( (code = sftpRemove(dest, !S_ISDIR(attr.permissions())) ) != SSH2_FX_OK ) {
- failed = true;
- }
- else {
- // XXX what if rename fails again? We have lost the file.
- // Maybe rename dest to a temporary name first? If rename is
- // successful, then delete?
- if( (code = sftpSymLink(target, dest)) != SSH2_FX_OK )
- failed = true;
- }
- }
+ SFTP_ATTRIBUTES *attr = sftp_stat(sftp_session, dest.constData());
+ if (bMarkPartial && attr != NULL) {
+ size_t size = config()->readEntry("MinimumKeepSize", DEFAULT_MINIMUM_KEEP_SIZE);
+ if (attr->size < size) {
+ sftp_unlink(sftp_session, dest.constData());
}
- else if( code == SSH2_FX_FAILURE ) {
- error(ERR_FILE_ALREADY_EXIST, dest.prettyUrl());
- return;
- }
- else
- failed = true;
+ }
+ delete attr;
+ sftp_attributes_free(attr);
}
- // What error code do we return? Code for the original symlink command
- // or for the last command or for both? The second one is implemented here.
- if( failed )
- processStatus(code);
-
+ //::exit(255);
finished();
-}
+ return;
+ }
-void sftpProtocol::chmod(const KUrl& url, int permissions){
- QString perms;
- perms.setNum(permissions, 8);
-
- kDebug(KIO_SFTP_DB) << "change permission of " << url << " to " << perms;
-
- openConnection();
- if( !mConnected )
- return;
-
- sftpFileAttr attr(remoteEncoding());
-
- if (permissions != -1)
- attr.setPermissions(permissions);
-
- int code;
- if( (code = sftpSetStat(url, attr)) != SSH2_FX_OK ) {
- kError(KIO_SFTP_DB) << "cannot stat failed with error " << code;
- if( code == SSH2_FX_FAILURE )
- error(ERR_CANNOT_CHMOD, QString());
- else
- processStatus(code, url.prettyUrl());
- }
+ if (file == NULL) { // we got nothing to write out, so we never opened the file
finished();
-}
+ return;
+ }
+ if (sftp_close(file) < 0) {
+ kWarning(KIO_SFTP_DB) << "Error when closing file descriptor";
+ error(KIO::ERR_COULD_NOT_WRITE, dest_orig);
+ return;
+ }
-void sftpProtocol::del(const KUrl &url, bool isfile){
- kDebug(KIO_SFTP_DB) << "delete " << (isfile ? "file: " : "directory: ") << url;
-
- openConnection();
- if( !mConnected )
- return;
-
- int code;
- if( (code = sftpRemove(url, isfile)) != SSH2_FX_OK ) {
- kError(KIO_SFTP_DB) << "delete failed with error code " << code;
- processStatus(code, url.prettyUrl());
+ // 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 ((flags & KIO::Overwrite)) {
+ sftp_unlink(sftp_session, dest_orig_c.constData());
}
- finished();
-}
-void sftpProtocol::slave_status() {
- kDebug(KIO_SFTP_DB) << "connected to " << mHost << "? " << mConnected;
- slaveStatus ((mConnected ? mHost : QString()), mConnected);
-}
-
-bool sftpProtocol::getPacket(QByteArray& msg) {
- QByteArray buf(4096, '\0');
-
-#ifdef Q_WS_WIN
- ssize_t len;
- if(ssh.pty()->waitForReadyRead(2000)) {
- len = ssh.pty()->read(buf.data(), 4);
+ if (sftp_rename(sftp_session, dest.constData(), dest_orig_c.constData()) < 0) {
+ kWarning(KIO_SFTP_DB) << " Couldn't rename " << dest << " to " << dest_orig;
+ error(KIO::ERR_CANNOT_RENAME_PARTIAL, dest_orig);
+ return;
}
-#else
- // Get the message length...
- ssize_t len = atomicio(ssh.stdioFd(), buf.data(), 4, true /*read*/);
-#endif
- if( len == 0 || len == -1 ) {
- kDebug(KIO_SFTP_DB) << "read of packet length failed, ret = "
- << len << ", error =" << strerror(errno);
- closeConnection();
- error( ERR_CONNECTION_BROKEN, mHost);
- msg.resize(0);
- return false;
- }
+ }
- int msgLen;
- QDataStream s(buf);
- s >> msgLen;
-
- //kDebug(KIO_SFTP_DB) << "message size = " << msgLen;
-
- msg.resize(0);
-
- QBuffer b( &msg );
- b.open( QIODevice::WriteOnly );
-
- while( msgLen ) {
-#ifdef Q_WS_WIN
- len = ssh.pty()->read(buf.data(), qMin(buf.size(), msgLen));
-#else
- len = atomicio(ssh.stdioFd(), buf.data(), qMin(buf.size(), msgLen), true /*read*/);
-#endif
-
- if( len == 0 || len == -1) {
- QString errmsg;
- if (len == 0)
- errmsg = i18n("Connection closed");
- else
- errmsg = i18n("Could not read SFTP packet");
- kDebug(KIO_SFTP_DB) << "nothing to read, ret = " << len << ", error =" << strerror(errno);
- closeConnection();
- error(ERR_CONNECTION_BROKEN, errmsg);
- b.close();
- return false;
- }
-
- b.write(buf.data(), len);
-
- //kDebug(KIO_SFTP_DB) << "read Message size = " << len;
- //kDebug(KIO_SFTP_DB) << "copy Message size = " << msg.size();
-
- msgLen -= len;
+ // set final permissions
+ if (permissions != -1 && !(flags & KIO::Resume)) {
+ if (sftp_chmod(sftp_session, dest_orig_c.constData(), permissions) < 0) {
+ kDebug(KIO_SFTP_DB) << "Could not change permissions for " << dest_orig;
+ //warning(i18n( "Could not change permissions for\n%1", dest_orig));
}
+ }
- b.close();
+ // set modification time
+ const QString mtimeStr = metaData("modified");
+ if (!mtimeStr.isEmpty()) {
+ QDateTime dt = QDateTime::fromString(mtimeStr, Qt::ISODate);
+ if (dt.isValid()) {
+ struct timeval times[2];
- return true;
-}
+ SFTP_ATTRIBUTES *attr = sftp_lstat(sftp_session, dest_orig_c.constData());
+ 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;
-/** Send an sftp packet to stdin of the ssh process. */
-bool sftpProtocol::putPacket(QByteArray& p){
-// kDebug(KIO_SFTP_DB) << "putPacket(): size == " << p.size();
- int ret;
-#ifdef Q_WS_WIN
- ret = ssh.pty()->write(p.data(), p.size());
-#else
- ret = atomicio(ssh.stdioFd(), p.data(), p.size(), false /*write*/);
-#endif
- if( ret <= 0 ) {
- kDebug(KIO_SFTP_DB) << "write failed, ret =" << ret << ", error = " << strerror(errno);
- return false;
+ sftp_utimes(sftp_session, dest_orig_c.constData(), times);
+ sftp_attributes_free(attr);
+ }
}
+ }
- return true;
+ // We have done our job => finish
+ finished();
}
-/** Used to have the server canonicalize any given path name to an absolute path.
-This is useful for converting path names containing ".." components or relative
-pathnames without a leading slash into absolute paths.
-Returns the canonicalized url. */
-int sftpProtocol::sftpRealPath(const KUrl& url, KUrl& newUrl){
-
- kDebug(KIO_SFTP_DB) << "get the real path of " << url;
-
- QByteArray path = remoteEncoding()->encode(url.path());
- uint len = path.length();
-
- quint32 id, expectedId;
- id = expectedId = mMsgId++;
-
- QByteArray p;
- QDataStream s(&p, QIODevice::WriteOnly);
- s << quint32(1 /*type*/ + 4 /*id*/ + 4 /*str length*/ + len);
- s << (quint8)SSH2_FXP_REALPATH;
- s << id;
- s.writeBytes(path.data(), len);
-
- putPacket(p);
- getPacket(p);
-
- quint8 type;
- QDataStream r(p);
-
- r >> type >> id;
- if( id != expectedId ) {
- kError(KIO_SFTP_DB) << "sftp packet id mismatch";
- return -1;
- }
-
- if( type == SSH2_FXP_STATUS ) {
- quint32 code;
- r >> code;
- return code;
- }
-
- if( type != SSH2_FXP_NAME ) {
- kError(KIO_SFTP_DB) << "unexpected packet type of " << type;
- return -1;
- }
-
- quint32 count;
- r >> count;
- if( count != 1 ) {
- kError(KIO_SFTP_DB) << "bad number of file attributes for realpath command";
- return -1;
- }
-
- QByteArray newPath;
- r >> newPath;
-
- newPath.truncate(newPath.size());
- if (newPath.isEmpty())
- newPath = "/";
- newUrl.setPath(remoteEncoding()->decode(newPath));
-
- kDebug(KIO_SFTP_DB) << "real path is " << newUrl;
- return SSH2_FX_OK;
-}
-
-sftpProtocol::Status sftpProtocol::doProcessStatus(quint8 code, const QString& message)
+void sftpProtocol::copy(const KUrl &src, const KUrl &dest, int permissions, KIO::JobFlags flags)
{
- Status res;
- res.code = 0;
- res.size = 0;
- res.text = message;
+ kDebug(KIO_SFTP_DB) << src << " -> " << dest << " , permissions = " << QString::number(permissions)
+ << ", overwrite = " << (flags & KIO::Overwrite)
+ << ", resume = " << (flags & KIO::Resume);
- switch(code)
- {
- case SSH2_FX_OK:
- case SSH2_FX_EOF:
- res.text = i18n("End of file.");
- break;
- case SSH2_FX_NO_SUCH_FILE:
- res.code = ERR_DOES_NOT_EXIST;
- break;
- case SSH2_FX_PERMISSION_DENIED:
- res.code = ERR_ACCESS_DENIED;
- break;
- case SSH2_FX_FAILURE:
- res.text = i18n("SFTP command failed for an unknown reason.");
- res.code = ERR_UNKNOWN;
- break;
- case SSH2_FX_BAD_MESSAGE:
- res.text = i18n("The SFTP server received a bad message.");
- res.code = ERR_UNKNOWN;
- break;
- case SSH2_FX_OP_UNSUPPORTED:
- res.text = i18n("You attempted an operation unsupported by the SFTP server.");
- res.code = ERR_UNKNOWN;
- break;
- default:
- res.text = i18n("Error code: %1", code);
- res.code = ERR_UNKNOWN;
- }
-
- return res;
+ error(ERR_UNSUPPORTED_ACTION, QString());
}
-/** Process SSH_FXP_STATUS packets. */
-void sftpProtocol::processStatus(quint8 code, const QString& message){
- Status st = doProcessStatus( code, message );
- if( st.code != 0 ){
- error( st.code, st.text );
- }
-}
+void sftpProtocol::stat(const KUrl& url) {
+ kDebug(KIO_SFTP_DB) << url;
-/** Opens a directory handle for url.path. Returns true if succeeds. */
-int sftpProtocol::sftpOpenDirectory(const KUrl& url, QByteArray& handle){
+ openConnection();
+ if (!mConnected) {
+ return;
+ }
- kDebug(KIO_SFTP_DB) << " open directory " << url;
+ if (! url.hasPath() || QDir::isRelativePath(url.path()) ||
+ url.path().contains("/./") || url.path().contains("/../")) {
+ QString cPath;
- QByteArray path = remoteEncoding()->encode(url.path());
- uint len = path.length();
-
- quint32 id, expectedId;
- id = expectedId = mMsgId++;
-
- QByteArray p;
- QDataStream s(&p, QIODevice::WriteOnly);
- s << (quint32)(1 /*type*/ + 4 /*id*/ + 4 /*str length*/ + len);
- s << (quint8)SSH2_FXP_OPENDIR;
- s << (quint32)id;
- s.writeBytes(path.data(), len);
-
- putPacket(p);
- getPacket(p);
-
- QDataStream r(p);
- quint8 type;
-
- r >> type >> id;
- if( id != expectedId ) {
- kError(KIO_SFTP_DB) << "sftp packet id mismatch: " <<
- "expected " << expectedId << ", got " << id;
- return -1;
+ if (url.hasPath()) {
+ cPath = canonicalizePath(url.path());
+ } else {
+ cPath = canonicalizePath(QString("."));
}
- if( type == SSH2_FXP_STATUS ) {
- quint32 errCode;
- r >> errCode;
- return errCode;
+ if (cPath.isEmpty()) {
+ error(ERR_MALFORMED_URL, url.prettyUrl());
+ return;
}
+ KUrl redir(url);
+ redir.setPath(cPath);
+ redirection(redir);
- if( type != SSH2_FXP_HANDLE ) {
- kError(KIO_SFTP_DB) << "unexpected message type of " << type;
- return -1;
- }
+ kDebug(KIO_SFTP_DB) << "redirecting to " << redir.url();
- r >> handle;
- if( handle.size() > 256 ) {
- kError(KIO_SFTP_DB) << "Handle exceeds max length";
- return -1;
- }
+ finished();
+ return;
+ }
- kDebug(KIO_SFTP_DB) << "directory handle (" << handle.size() << "): [" << handle << "]";
- return SSH2_FX_OK;
-}
+ QByteArray path = url.path().toUtf8();
-/** Closes a directory or file handle. */
-int sftpProtocol::sftpClose(const QByteArray& handle){
+ const QString sDetails = metaData(QLatin1String("details"));
+ const int details = sDetails.isEmpty() ? 2 : sDetails.toInt();
- quint32 id, expectedId;
- id = expectedId = mMsgId++;
+ UDSEntry entry;
+ entry.clear();
+ if (!createUDSEntry(url.fileName(), path, entry, details)) {
+ error(ERR_DOES_NOT_EXIST, url.prettyUrl());
+ return;
+ }
- QByteArray p;
- QDataStream s(&p, QIODevice::WriteOnly);
- s << (quint32)(1 /*type*/ + 4 /*id*/ + 4 /*str length*/ + handle.size());
- s << (quint8)SSH2_FXP_CLOSE;
- s << (quint32)id;
- s << handle;
+ statEntry(entry);
- putPacket(p);
- getPacket(p);
-
- QDataStream r(p);
- quint8 type;
-
- r >> type >> id;
- if( id != expectedId ) {
- kError(KIO_SFTP_DB) << "sftp packet id mismatch";
- return -1;
- }
-
- if( type != SSH2_FXP_STATUS ) {
- kError(KIO_SFTP_DB) << "unexpected message type of " << type;
- return -1;
- }
-
- quint32 code;
- r >> code;
- if( code != SSH2_FX_OK ) {
- kError(KIO_SFTP_DB) << "close failed with err code " << code;
- }
-
- return code;
+ finished();
}
-/** Set a files attributes. */
-int sftpProtocol::sftpSetStat(const KUrl& url, const sftpFileAttr& attr){
+void sftpProtocol::mimetype(const KUrl& url){
+ kDebug(KIO_SFTP_DB) << url;
- kDebug(KIO_SFTP_DB) << "stating url " << url;
+ openConnection();
+ if (!mConnected) {
+ return;
+ }
- QByteArray path = remoteEncoding()->encode(url.path());
- uint len = path.length();
+ // open() feeds the mimetype
+ open(url, QIODevice::ReadOnly);
+ close();
- quint32 id, expectedId;
- id = expectedId = mMsgId++;
-
- QByteArray p;
- QDataStream s(&p, QIODevice::WriteOnly);
- s << (quint32)(1 /*type*/ + 4 /*id*/ + 4 /*str length*/ + len + attr.size());
- s << (quint8)SSH2_FXP_SETSTAT;
- s << (quint32)id;
- s.writeBytes(path.data(), len);
- s << attr;
-
- putPacket(p);
- getPacket(p);
-
- QDataStream r(p);
- quint8 type;
-
- r >> type >> id;
- if( id != expectedId ) {
- kError(KIO_SFTP_DB) << "sftp packet id mismatch";
- return -1;
- // XXX How do we do a fatal error?
- }
-
- if( type != SSH2_FXP_STATUS ) {
- kError(KIO_SFTP_DB) << "unexpected message type of " << type;
- return -1;
- }
-
- quint32 code;
- r >> code;
- if( code != SSH2_FX_OK ) {
- kError(KIO_SFTP_DB) << "set stat failed with err code " << code;
- }
-
- return code;
+ finished();
}
-/** Sends a sftp command to remove a file or directory. */
-int sftpProtocol::sftpRemove(const KUrl& url, bool isfile){
+void sftpProtocol::listDir(const KUrl& url) {
+ kDebug(KIO_SFTP_DB) << "list directory: " << url;
- kDebug(KIO_SFTP_DB) << "deleting " << (isfile ? "file " : "directory ") << url;
+ openConnection();
+ if (!mConnected) {
+ return;
+ }
- QByteArray path = remoteEncoding()->encode(url.path());
- uint len = path.length();
+ if (! url.hasPath() || QDir::isRelativePath(url.path()) ||
+ url.path().contains("/./") || url.path().contains("/../")) {
+ QString cPath;
- quint32 id, expectedId;
- id = expectedId = mMsgId++;
-
- QByteArray p;
- QDataStream s(&p, QIODevice::WriteOnly);
- s << (quint32)(1 /*type*/ + 4 /*id*/ + 4 /*str length*/ + len);
- s << (quint8)(isfile ? SSH2_FXP_REMOVE : SSH2_FXP_RMDIR);
- s << (quint32)id;
- s.writeBytes(path.data(), len);
-
- putPacket(p);
- getPacket(p);
-
- QDataStream r(p);
- quint8 type;
-
- r >> type >> id;
- if( id != expectedId ) {
- kError(KIO_SFTP_DB) << "sftp packet id mismatch";
- return -1;
+ if (url.hasPath()) {
+ cPath = canonicalizePath(url.path());
+ } else {
+ cPath = canonicalizePath(QString("."));
}
- if( type != SSH2_FXP_STATUS ) {
- kError(KIO_SFTP_DB) << "unexpected message type of " << type;
- return -1;
+ if (cPath.isEmpty()) {
+ error(ERR_MALFORMED_URL, url.prettyUrl());
+ return;
}
+ KUrl redir(url);
+ redir.setPath(cPath);
+ redirection(redir);
- quint32 code;
- r >> code;
- if( code != SSH2_FX_OK ) {
- kError(KIO_SFTP_DB) << "delete failed with error code " << code;
- }
+ kDebug(KIO_SFTP_DB) << "redirecting to " << redir.url();
- return code;
-}
+ finished();
+ return;
+ }
-/** Send a sftp command to rename a file or directory. */
-int sftpProtocol::sftpRename(const KUrl& src, const KUrl& dest){
+ QByteArray path = url.path().toUtf8();
- kDebug(KIO_SFTP_DB) << src << " -> " << dest;
+ SFTP_DIR *dp = sftp_opendir(sftp_session, path.constData());
+ if (dp == NULL) {
+ reportError(url, sftp_get_error(sftp_session));
+ return;
+ }
- QByteArray srcPath = remoteEncoding()->encode(src.path());
- QByteArray destPath = remoteEncoding()->encode(dest.path());
+ SFTP_ATTRIBUTES *dirent = NULL;
+ const QString sDetails = metaData(QLatin1String("details"));
+ const int details = sDetails.isEmpty() ? 2 : sDetails.toInt();
+ QList<QByteArray> entryNames;
+ UDSEntry entry;
- uint slen = srcPath.length();
- uint dlen = destPath.length();
+ kDebug(KIO_SFTP_DB) << "readdir: " << path << ", details: " << QString::number(details);
+ if (details == 0) {
+ for (;;) {
+ dirent = sftp_readdir(sftp_session, dp);
+ if (dirent == NULL) {
+ break;
+ }
- quint32 id, expectedId;
- id = expectedId = mMsgId++;
+ entry.clear();
+ entry.insert(KIO::UDSEntry::UDS_NAME, QFile::decodeName(dirent->name));
- QByteArray p;
- QDataStream s(&p, QIODevice::WriteOnly);
- s << (quint32)(1 /*type*/ + 4 /*id*/ +
- 4 /*str length*/ + slen +
- 4 /*str length*/ + dlen);
- s << (quint8)SSH2_FXP_RENAME;
- s << (quint32)id;
- s.writeBytes(srcPath.data(), slen);
- s.writeBytes(destPath.data(), dlen);
+ switch (dirent->type) {
+ case SSH_FILEXFER_TYPE_REGULAR:
+ entry.insert(KIO::UDSEntry::UDS_FILE_TYPE, S_IFREG);
+ break;
+ case SSH_FILEXFER_TYPE_DIRECTORY:
+ entry.insert(KIO::UDSEntry::UDS_FILE_TYPE, S_IFDIR);
+ break;
+ case SSH_FILEXFER_TYPE_SYMLINK:
+ entry.insert(KIO::UDSEntry::UDS_FILE_TYPE, S_IFREG);
+ entry.insert(KIO::UDSEntry::UDS_LINK_DEST, QLatin1String("Dummy Link Target"));
+ break;
+ case SSH_FILEXFER_TYPE_SPECIAL:
+ case SSH_FILEXFER_TYPE_UNKNOWN:
+ break;
+ }
- putPacket(p);
- getPacket(p);
-
- QDataStream r(p);
- quint8 type;
-
- r >> type >> id;
- if( id != expectedId ) {
- kError(KIO_SFTP_DB) << "sftp packet id mismatch";
- return -1;
+ sftp_attributes_free(dirent);
+ listEntry(entry, false);
}
+ sftp_closedir(dp);
+ listEntry(entry, true); // ready
+ } else {
+ for (;;) {
+ dirent = sftp_readdir(sftp_session, dp);
+ if (dirent == NULL) {
+ break;
+ }
- if( type != SSH2_FXP_STATUS ) {
- kError(KIO_SFTP_DB) << "unexpected message type of " << type;
- return -1;
+ entryNames.append(dirent->name);
+ sftp_attributes_free(dirent);
}
- int code;
- r >> code;
- if( code != SSH2_FX_OK ) {
- kError(KIO_SFTP_DB) << "rename failed with err code " << code;
- }
+ sftp_closedir(dp);
+ totalSize(entryNames.count());
- return code;
-}
-/** Get directory listings. */
-int sftpProtocol::sftpReadDir(const QByteArray& handle, const KUrl& url){
- // url is needed so we can lookup the link destination
- kDebug(KIO_SFTP_DB) << url;
+ QList<QByteArray>::ConstIterator it = entryNames.constBegin();
+ QList<QByteArray>::ConstIterator end = entryNames.constEnd();
- quint32 id, expectedId, count;
- quint8 type;
-
- sftpFileAttr attr (remoteEncoding());
- attr.setDirAttrsFlag(true);
-
- QByteArray p;
- QDataStream s(&p, QIODevice::WriteOnly);
- id = expectedId = mMsgId++;
- s << (quint32)(1 /*type*/ + 4 /*id*/ + 4 /*str length*/ + handle.size());
- s << (quint8)SSH2_FXP_READDIR;
- s << (quint32)id;
- s << handle;
-
- putPacket(p);
- getPacket(p);
-
- QDataStream r(p);
- r >> type >> id;
-
- if( id != expectedId ) {
- kError(KIO_SFTP_DB) << "sftp packet id mismatch";
- return -1;
+ for (; it != end; ++it) {
+ entry.clear();
+ if (createUDSEntry(QFile::decodeName(*it), QByteArray(path + "/" + *it).constData(), entry, details)) {
+ listEntry(entry, false);
+ }
}
+ listEntry(entry, true); // ready
+ }
- int code;
- if( type == SSH2_FXP_STATUS ) {
- r >> code;
- return code;
- }
-
- if( type != SSH2_FXP_NAME ) {
- kError(KIO_SFTP_DB) << "unexpected message";
- return -1;
- }
-
- r >> count;
- kDebug(KIO_SFTP_DB) << "got " << count << " entries";
-
- while(count--) {
- r >> attr;
-
- if( S_ISLNK(attr.permissions()) ) {
- KUrl myurl ( url );
- myurl.addPath(attr.filename());
-
- // Stat the symlink to find out its type...
- sftpFileAttr attr2 (remoteEncoding());
- (void) sftpStat(myurl, attr2);
-
- attr.setLinkType(attr2.linkType());
- attr.setLinkDestination(attr2.linkDestination());
- }
-
- listEntry(attr.entry(), false);
- }
-
- listEntry(attr.entry(), true);
-
- return SSH2_FX_OK;
+ finished();
}
-int sftpProtocol::sftpReadLink(const KUrl& url, QString& target){
+void sftpProtocol::mkdir(const KUrl &url, int permissions) {
+ kDebug(KIO_SFTP_DB) << "create directory: " << url;
- kDebug(KIO_SFTP_DB) << url;
+ openConnection();
+ if (!mConnected) {
+ return;
+ }
- QByteArray path = remoteEncoding()->encode(url.path());
- uint len = path.length();
+ if (url.path().isEmpty()) {
+ error(ERR_MALFORMED_URL, url.prettyUrl());
+ return;
+ }
+ const QString path = url.path();
+ const QByteArray path_c = path.toUtf8();
- //kDebug(KIO_SFTP_DB) << "Encoded Path: " << path;
- //kDebug(KIO_SFTP_DB) << "Encoded Size: " << len;
+ // Remove existing file or symlink, if requested.
+ if (metaData(QLatin1String("overwrite")) == QLatin1String("true")) {
+ kDebug(KIO_SFTP_DB) << "overwrite set, remove existing file or symlink: " << url;
+ sftp_unlink(sftp_session, path_c.constData());
+ }
- quint32 id, expectedId;
- id = expectedId = mMsgId++;
-
- QByteArray p;
- QDataStream s(&p, QIODevice::WriteOnly);
- s << (quint32)(1 /*type*/ + 4 /*id*/ + 4 /*str length*/ + len);
- s << (quint8)SSH2_FXP_READLINK;
- s << id;
- s.writeBytes(path.data(), len);
-
-
- putPacket(p);
- getPacket(p);
-
- quint8 type;
- QDataStream r(p);
-
- r >> type >> id;
- if( id != expectedId ) {
- kError(KIO_SFTP_DB) << "sftp packet id mismatch";
- return -1;
+ kDebug(KIO_SFTP_DB) << "Trying to create directory: " << path;
+ SFTP_ATTRIBUTES *sb = sftp_lstat(sftp_session, path_c.constData());
+ if (sb == NULL) {
+ if (sftp_mkdir(sftp_session, path_c.constData(), 0777) < 0) {
+ reportError(url, sftp_get_error(sftp_session));
+ sftp_attributes_free(sb);
+ return;
+ } else {
+ kDebug(KIO_SFTP_DB) << "Successfully created directory: " << url;
+ if (permissions != -1) {
+ chmod(url, permissions);
+ } else {
+ finished();
+ }
+ sftp_attributes_free(sb);
+ return;
}
+ }
- if( type == SSH2_FXP_STATUS ) {
- quint32 code;
- r >> code;
- kDebug(KIO_SFTP_DB) << "read link failed with code " << code;
- return code;
- }
+ if (sb->type == SSH_FILEXFER_TYPE_DIRECTORY) {
+ error(KIO::ERR_DIR_ALREADY_EXIST, path);
+ } else {
+ error(KIO::ERR_FILE_ALREADY_EXIST, path);
+ }
- if( type != SSH2_FXP_NAME ) {
- kError(KIO_SFTP_DB) << "unexpected packet type of " << type;
- return -1;
- }
-
- quint32 count;
- r >> count;
- if( count != 1 ) {
- kError(KIO_SFTP_DB) << "bad number of file attributes for realpath command";
- return -1;
- }
-
- QByteArray linkAddress;
- r >> linkAddress;
-
- linkAddress.truncate(linkAddress.size());
- kDebug(KIO_SFTP_DB) << "link address: " << linkAddress;
-
- target = remoteEncoding()->decode(linkAddress);
-
- return SSH2_FX_OK;
+ sftp_attributes_free(sb);
+ return;
}
-int sftpProtocol::sftpSymLink(const QString& _target, const KUrl& dest){
+void sftpProtocol::rename(const KUrl& src, const KUrl& dest, KIO::JobFlags flags) {
+ kDebug(KIO_SFTP_DB) << "rename " << src << " to " << dest;
- QByteArray destPath = remoteEncoding()->encode(dest.path());
- QByteArray target = remoteEncoding()->encode(_target);
- uint dlen = destPath.length();
- uint tlen = target.length();
+ openConnection();
+ if (!mConnected) {
+ return;
+ }
- kDebug(KIO_SFTP_DB) << "(" << target << " -> " << destPath << ")";
+ QByteArray qsrc = src.path().toUtf8();
+ QByteArray qdest = dest.path().toUtf8();
- quint32 id, expectedId;
- id = expectedId = mMsgId++;
-
- QByteArray p;
- QDataStream s(&p, QIODevice::WriteOnly);
- s << (quint32)(1 /*type*/ + 4 /*id*/ +
- 4 /*str length*/ + tlen +
- 4 /*str length*/ + dlen);
- s << (quint8)SSH2_FXP_SYMLINK;
- s << (quint32)id;
- s.writeBytes(target.data(), tlen);
- s.writeBytes(destPath.data(), dlen);
-
- putPacket(p);
- getPacket(p);
-
- QDataStream r(p);
- quint8 type;
-
- r >> type >> id;
- if( id != expectedId ) {
- kError(KIO_SFTP_DB) << "sftp packet id mismatch";
- return -1;
+ SFTP_ATTRIBUTES *sb = sftp_lstat(sftp_session, qdest.constData());
+ if (sb != NULL) {
+ if (!(flags & KIO::Overwrite)) {
+ if (sb->type == SSH_FILEXFER_TYPE_DIRECTORY) {
+ error(KIO::ERR_DIR_ALREADY_EXIST, dest.url());
+ } else {
+ error(KIO::ERR_FILE_ALREADY_EXIST, dest.url());
+ }
+ sftp_attributes_free(sb);
+ return;
}
- if( type != SSH2_FXP_STATUS ) {
- kError(KIO_SFTP_DB) << "unexpected message type of " << type;
- return -1;
- }
+ del(dest, sb->type == SSH_FILEXFER_TYPE_DIRECTORY ? true : false);
+ }
+ sftp_attributes_free(sb);
- quint32 code;
- r >> code;
- if( code != SSH2_FX_OK ) {
- kError(KIO_SFTP_DB) << "rename failed with err code " << code;
- }
+ if (sftp_rename(sftp_session, qsrc.constData(), qdest.constData()) < 0) {
+ reportError(dest, sftp_get_error(sftp_session));
+ return;
+ }
- return code;
+ finished();
}
-/** Stats a file. */
-int sftpProtocol::sftpStat(const KUrl& url, sftpFileAttr& attr) {
+void sftpProtocol::symlink(const QString &target, const KUrl &dest, KIO::JobFlags flags) {
+ kDebug(KIO_SFTP_DB) << "link " << target << "->" << dest
+ << ", overwrite = " << (flags & KIO::Overwrite)
+ << ", resume = " << (flags & KIO::Resume);
- kDebug(KIO_SFTP_DB) << url;
+ openConnection();
+ if (!mConnected) {
+ return;
+ }
- QByteArray path = remoteEncoding()->encode(url.path());
- uint len = path.length();
+ QByteArray t = target.toUtf8();
+ QByteArray d = dest.path().toUtf8();
- quint32 id, expectedId;
- id = expectedId = mMsgId++;
-
- QByteArray p;
- QDataStream s(&p, QIODevice::WriteOnly);
- s << (quint32)(1 /*type*/ + 4 /*id*/ + 4 /*str length*/ + len);
- s << (quint8)SSH2_FXP_LSTAT;
- s << (quint32)id;
- s.writeBytes(path.data(), len);
-
- putPacket(p);
- getPacket(p);
-
- QDataStream r(p);
- quint8 type;
-
- r >> type >> id;
- if( id != expectedId ) {
- kError(KIO_SFTP_DB) << "sftp packet id mismatch";
- return -1;
- }
-
- if( type == SSH2_FXP_STATUS ) {
- quint32 errCode;
- r >> errCode;
- kError(KIO_SFTP_DB) << "stat failed with code " << errCode;
- return errCode;
- }
-
- if( type != SSH2_FXP_ATTRS ) {
- kError(KIO_SFTP_DB) << "unexpected message type of " << type;
- return -1;
- }
-
- r >> attr;
- attr.setFilename(url.fileName());
- kDebug(KIO_SFTP_DB) << attr;
-
- // If the stat'ed resource is a symlink, perform a recursive stat
- // to determine the actual destination's type (file/dir).
- if( S_ISLNK(attr.permissions()) && isSupportedOperation(SSH2_FXP_READLINK) ) {
-
- QString target;
- int code = sftpReadLink( url, target );
-
- if ( code != SSH2_FX_OK ) {
- kError(KIO_SFTP_DB) << "unable to stat symlink destination";
- return -1;
+ bool failed = false;
+ if (sftp_symlink(sftp_session, t.constData(), d.constData()) < 0) {
+ if (flags == KIO::Overwrite) {
+ SFTP_ATTRIBUTES *sb = sftp_lstat(sftp_session, d.constData());
+ if (sb == NULL) {
+ failed = true;
+ } else {
+ if (sftp_unlink(sftp_session, d.constData()) < 0) {
+ failed = true;
+ } else {
+ if (sftp_symlink(sftp_session, t.constData(), d.constData()) < 0) {
+ failed = true;
+ }
}
-
- kDebug(KIO_SFTP_DB) << "resource is a symlink that points to " << target;
-
- KUrl dest( url );
- if( target[0] == '/' )
- dest.setPath(target);
- else
- dest.setFileName(target);
-
- dest.cleanPath();
-
- // Ignore symlinks that point to themselves...
- if ( dest != url ) {
-
- sftpFileAttr attr2 (remoteEncoding());
- (void) sftpStat(dest, attr2);
-
- if (attr2.linkType() == 0)
- attr.setLinkType(attr2.fileType());
- else
- attr.setLinkType(attr2.linkType());
-
- attr.setLinkDestination(target);
- attr.setPermissions(attr2.permissions());
- }
+ }
+ sftp_attributes_free(sb);
}
+ }
- return SSH2_FX_OK;
+ if (failed) {
+ reportError(dest, sftp_get_error(sftp_session));
+ return;
+ }
+
+ finished();
}
+void sftpProtocol::chmod(const KUrl& url, int permissions) {
+ kDebug(KIO_SFTP_DB) << "change permission of " << url << " to " << QString::number(permissions);
-int sftpProtocol::sftpOpen(const KUrl& url, const quint32 pflags,
- const sftpFileAttr& attr, QByteArray& handle) {
- kDebug(KIO_SFTP_DB) << url;
+ openConnection();
+ if (!mConnected) {
+ return;
+ }
- QByteArray path = remoteEncoding()->encode(url.path());
- uint len = path.length();
+ QByteArray path = url.path().toUtf8();
- quint32 id, expectedId;
- id = expectedId = mMsgId++;
+ if (sftp_chmod(sftp_session, path.constData(), permissions) < 0) {
+ reportError(url, sftp_get_error(sftp_session));
+ return;
+ }
- QByteArray p;
- QDataStream s(&p, QIODevice::WriteOnly);
- s << (quint32)(1 /*type*/ + 4 /*id*/ +
- 4 /*str length*/ + len +
- 4 /*pflags*/ + attr.size());
- s << (quint8)SSH2_FXP_OPEN;
- s << (quint32)id;
- s.writeBytes(path.data(), len);
- s << pflags;
- s << attr;
-
- putPacket(p);
- getPacket(p);
-
- QDataStream r(p);
- quint8 type;
-
- r >> type >> id;
- if( id != expectedId ) {
- kError(KIO_SFTP_DB) << "sftp packet id mismatch";
- return -1;
- }
-
- if( type == SSH2_FXP_STATUS ) {
- quint32 errCode;
- r >> errCode;
- return errCode;
- }
-
- if( type != SSH2_FXP_HANDLE ) {
- kError(KIO_SFTP_DB) << "unexpected message type of " << type;
- return -1;
- }
-
- r >> handle;
- if( handle.size() > 256 ) {
- kError(KIO_SFTP_DB) << "handle exceeds max length";
- return -1;
- }
-
- kDebug(KIO_SFTP_DB) << "URL handle (" << handle.size() << "): [" << handle << "]";
- return SSH2_FX_OK;
+ finished();
}
+void sftpProtocol::del(const KUrl &url, bool isfile){
+ kDebug(KIO_SFTP_DB) << "deleting " << (isfile ? "file: " : "directory: ") << url;
-int sftpProtocol::sftpRead(const QByteArray& handle, KIO::filesize_t offset, quint32 len, QByteArray& data)
-{
- // kDebug(KIO_SFTP_DB) << "( offset = " << offset << ", len = " << len << ")";
- QByteArray p;
- QDataStream s(&p, QIODevice::WriteOnly);
+ openConnection();
+ if (!mConnected) {
+ return;
+ }
- quint32 id, expectedId;
- id = expectedId = mMsgId++;
- s << (quint32)(1 /*type*/ + 4 /*id*/ +
- 4 /*str length*/ + handle.size() +
- 8 /*offset*/ + 4 /*length*/);
- s << (quint8)SSH2_FXP_READ;
- s << (quint32)id;
- s << handle;
- s << offset; // we don't have a convienient 64 bit int so set upper int to zero
- s << len;
+ QByteArray path = url.path().toUtf8();
- putPacket(p);
- getPacket(p);
-
- QDataStream r(p);
- quint8 type;
-
- r >> type >> id;
- if( id != expectedId ) {
- kError(KIO_SFTP_DB) << "sftp packet id mismatch";
- return -1;
+ if (isfile) {
+ if (sftp_unlink(sftp_session, path.constData()) < 0) {
+ reportError(url, sftp_get_error(sftp_session));
+ return;
}
-
- if( type == SSH2_FXP_STATUS ) {
- quint32 errCode;
- r >> errCode;
- kError(KIO_SFTP_DB) << "Read failed with code " << errCode;
- return errCode;
+ } else {
+ if (sftp_rmdir(sftp_session, path.constData()) < 0) {
+ reportError(url, sftp_get_error(sftp_session));
+ return;
}
+ }
- if( type != SSH2_FXP_DATA ) {
- kError(KIO_SFTP_DB) << "unexpected message type of " << type;
- return -1;
- }
-
- r >> data;
-
- return SSH2_FX_OK;
+ finished();
}
-
-int sftpProtocol::sftpWrite(const QByteArray& handle, KIO::filesize_t offset, const QByteArray& data){
-// kDebug(KIO_SFTP_DB) << "sftpWrite( offset = " << offset <<
-// ", data sz = " << data.size() << ")";
- QByteArray p;
- QDataStream s(&p, QIODevice::WriteOnly);
-
- quint32 id, expectedId;
- id = expectedId = mMsgId++;
- s << (quint32)(1 /*type*/ + 4 /*id*/ +
- 4 /*str length*/ + handle.size() +
- 8 /*offset*/ +
- 4 /* data size */ + data.size());
- s << (quint8)SSH2_FXP_WRITE;
- s << (quint32)id;
- s << handle;
- s << offset; // we don't have a convienient 64 bit int so set upper int to zero
- s << data;
-
-// kDebug(KIO_SFTP_DB) << "SSH2_FXP_WRITE, id:"
-// << id << ", handle:" << handle << ", offset:" << offset << ", some data";
-
-// kDebug(KIO_SFTP_DB) << "Send packet [" << p << "]";
-
- putPacket(p);
- getPacket(p);
-
-// kDebug(KIO_SFTP_DB) << "Received packet [" << p << "]";
-
- QDataStream r(p);
- quint8 type;
-
- r >> type >> id;
- if( id != expectedId ) {
- kError(KIO_SFTP_DB) << "sftp packet id mismatch, got "
- << id << ", expected " << expectedId;
- return -1;
- }
-
- if( type != SSH2_FXP_STATUS ) {
- kError(KIO_SFTP_DB) << "unexpected message type of " << type;
- return -1;
- }
-
- quint32 code;
- r >> code;
- return code;
+void sftpProtocol::slave_status() {
+ kDebug(KIO_SFTP_DB) << "connected to " << mHost << "?: " << mConnected;
+ slaveStatus((mConnected ? mHost : QString()), mConnected);
}
+
Index: runtime/kioslave/sftp/kio_sftp.h
===================================================================
--- runtime/kioslave/sftp/kio_sftp.h (revision 1026868)
+++ runtime/kioslave/sftp/kio_sftp.h (working copy)
@@ -1,36 +1,43 @@
-/***************************************************************************
- sftpProtocol.h - description
- -------------------
- begin : Sat Jun 30 20:08:47 CDT 2001
- copyright : (C) 2001 by Lucas Fisher
- email : ljfisher@purdue.edu
-***************************************************************************/
+/*
+ * Copyright (c) 2001 Lucas Fisher <ljfisher@purdue.edu>
+ * Copyright (c) 2009 Andreas Schneider <mail@cynapses.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.
+ */
-/***************************************************************************
- * *
- * This program is free software; you can redistribute it and/or modify *
- * it under the terms of the GNU General Public License as published by *
- * the Free Software Foundation; either version 2 of the License, or *
- * (at your option) any later version. *
- * *
- ***************************************************************************/
#ifndef __kio_sftp_h__
#define __kio_sftp_h__
-
-#include <QObject>
-
#include <kurl.h>
#include <kio/global.h>
#include <kio/slavebase.h>
#include <kdebug.h>
-#include "process.h"
-#include "sftpfileattr.h"
-#include "ksshprocess.h"
+#include <libssh/libssh.h>
+#include <libssh/sftp.h>
+// How big should each data packet be? Definitely not bigger than 64kb or
+// you will overflow the 2 byte size variable in a sftp packet.
+#define MAX_XFER_BUF_SIZE 60 * 1024
#define KIO_SFTP_DB 7120
+namespace KIO {
+ class AuthInfo;
+}
class sftpProtocol : public KIO::SlaveBase
{
@@ -38,20 +45,20 @@
public:
sftpProtocol(const QByteArray &pool_socket, const QByteArray &app_socket);
virtual ~sftpProtocol();
- virtual void setHost(const QString& h, quint16 port, const QString& user, const QString& pass);
- virtual void get(const KUrl& url);
- virtual void listDir(const KUrl& url) ;
- virtual void mimetype(const KUrl& url);
- virtual void stat(const KUrl& url);
+ virtual void setHost(const QString &h, quint16 port, const QString& user, const QString& pass);
+ virtual void get(const KUrl &url);
+ virtual void listDir(const KUrl &url) ;
+ virtual void mimetype(const KUrl &url);
+ virtual void stat(const KUrl &url);
virtual void copy(const KUrl &src, const KUrl &dest, int permissions, KIO::JobFlags flags);
- virtual void put(const KUrl& url, int permissions, KIO::JobFlags flags);
+ virtual void put(const KUrl &url, int permissions, KIO::JobFlags flags);
virtual void closeConnection();
virtual void slave_status();
virtual void del(const KUrl &url, bool isfile);
- virtual void chmod(const KUrl& url, int permissions);
- virtual void symlink(const QString& target, const KUrl& dest, KIO::JobFlags flags);
- virtual void rename(const KUrl& src, const KUrl& dest, KIO::JobFlags flags);
- virtual void mkdir(const KUrl&url, int permissions);
+ virtual void chmod(const KUrl &url, int permissions);
+ virtual void symlink(const QString &target, const KUrl &dest, KIO::JobFlags flags);
+ virtual void rename(const KUrl &src, const KUrl &dest, KIO::JobFlags flags);
+ virtual void mkdir(const KUrl &url, int permissions);
virtual void openConnection();
// KIO::FileJob interface
@@ -61,6 +68,10 @@
virtual void seek(KIO::filesize_t offset);
virtual void close();
+ // libssh authentication callback (note that this is called by the
+ // global ::auth_callback() call.
+ int auth_callback(const char *prompt, char *buf, size_t len,
+ int echo, int verify, void *userdata);
private: // Private variables
/** True if ioslave is connected to sftp server. */
bool mConnected;
@@ -71,20 +82,23 @@
/** Port we are connected to. */
int mPort;
- /** Ssh process to which we send the sftp packets. */
- KSshProcess ssh;
+ /** The ssh session for the connection */
+ SSH_SESSION *ssh_session;
+ /** The sftp session for the connection */
+ SFTP_SESSION *sftp_session;
+
/** Username to use when connecting */
QString mUsername;
/** User's password */
QString mPassword;
- /** Message id of the last sftp packet we sent. */
- unsigned int mMsgId;
+ /** The open file */
+ SFTP_FILE *mOpenFile;
- /** Type of packet we are expecting to receive next. */
- unsigned char mExpected;
+ /** The open URL */
+ KUrl mOpenUrl;
/** Version of the sftp protocol we are using. */
int sftpVersion;
@@ -103,60 +117,15 @@
KIO::filesize_t openOffset;
private: // private methods
- bool getPacket(QByteArray& msg);
- /* Type is a sftp packet type found in .sftp.h'.
- * Example: SSH2_FXP_READLINK, SSH2_FXP_RENAME, etc.
- *
- * Returns true if the type is supported by the sftp protocol
- * version negotiated by the client and server (sftpVersion).
- */
- bool isSupportedOperation(int type);
- /** Used to have the server canonicalize any given path name to an absolute path.
- This is useful for converting path names containing ".." components or relative
- pathnames without a leading slash into absolute paths.
- Returns the canonicalized url. */
- int sftpRealPath(const KUrl& url, KUrl& newUrl);
+ int authenticateKeyboardInteractive(KIO::AuthInfo &info);
- /** Send an sftp packet to stdin of the ssh process. */
- bool putPacket(QByteArray& p);
- /** Process SSH_FXP_STATUS packets. */
- void processStatus(quint8, const QString& message = QString());
- /** Process SSH_FXP_STATUS packes and return the result. */
- Status doProcessStatus(quint8, const QString& message = QString());
- /** Opens a directory handle for url.path. Returns true if succeeds. */
- int sftpOpenDirectory(const KUrl& url, QByteArray& handle);
- /** Closes a directory or file handle. */
- int sftpClose(const QByteArray& handle);
- /** Send a sftp command to rename a file or directory. */
- int sftpRename(const KUrl& src, const KUrl& dest);
- /** Set a files attributes. */
- int sftpSetStat(const KUrl& url, const sftpFileAttr& attr);
- /** Sends a sftp command to remove a file or directory. */
- int sftpRemove(const KUrl& url, bool isfile);
- /** Creates a symlink named dest to target. */
- int sftpSymLink(const QString& target, const KUrl& dest);
- /** Get directory listings. */
- int sftpReadDir(const QByteArray& handle, const KUrl& url);
- /** Retrieves the destination of a link. */
- int sftpReadLink(const KUrl& url, QString& target);
- /** Stats a file. */
- int sftpStat(const KUrl& url, sftpFileAttr& attr);
- /** No descriptions */
- int sftpOpen(const KUrl& url, const quint32 pflags, const sftpFileAttr& attr, QByteArray& handle);
- /** No descriptions */
- int sftpRead(const QByteArray& handle, KIO::filesize_t offset, quint32 len, QByteArray& data);
- /** No descriptions */
- int sftpWrite(const QByteArray& handle, KIO::filesize_t offset, const QByteArray& data);
+ void reportError(const KUrl &url, const int err);
- /** Performs faster upload when the source is a local file... */
- void sftpCopyPut(const KUrl& src, const KUrl& dest, int mode, KIO::JobFlags flags);
- /** Performs faster download when the destination is a local file... */
- void sftpCopyGet(const KUrl& dest, const KUrl& src, int mode, KIO::JobFlags flags);
+ bool createUDSEntry(const QString &filename, const QByteArray &path,
+ KIO::UDSEntry &entry, short int details);
- /** Read a file. This is used by get(), copy(), and mimetype(). */
- Status sftpGet( const KUrl& src, KIO::filesize_t offset = 0, int fd = -1, bool abortAfterMimeType = false);
- /** Write a file */
- void sftpPut( const KUrl& dest, int permissions, KIO::JobFlags flags, int fd = -1);
+ QString canonicalizePath(const QString &path);
};
+
#endif
Index: runtime/kioslave/sftp/TODO
===================================================================
--- runtime/kioslave/sftp/TODO (revision 1026868)
+++ runtime/kioslave/sftp/TODO (working copy)
@@ -1,51 +1,4 @@
TODO:
-- Password caching doesn't work. We get a new dialog box every time a new kio_sftp process starts.
- Very annoying.
-
-- Support for use of public keys, maybe ssh-agent, a key management app, etc.
-
-- bug: when changing hostname in locationbar, password dialog pops up at
- every change even without pressing enter
- - Bug report submitted to konqueror
-
-- bug: kio_sftp crashes KDE completely when one or more of konq, kio and
- kio_sftp are compiled with the objprelink patches. None of the other
- kioslaves do this for me, but it also happened when I recompiled kio_sftp
- without the preloading, so I'm unsure whether it's a kio_sftp or kio or
- konq bug. Will investigate some more. (Rob)
-
-COMPLETED
-=========
-
-- Orphaned ssh processes are bad. Shame on me. - Done
- Use SIGKILL to kill the ssh process. The ssh client seems to catch
- SIGTERM from its parent and ignore them.
-
-- Continuing partial upload - doesn't create .part file.d
- fixed I think, but I am unable to test resume. kio denies my resume request - Done
-
-- When typing sftp url into konqi, konqi tries to connect before we finish
- typing url. Konqi is trying to do directory completion, but we don't
- want this when connecting to a remote server the first time. kio_ftp
- doesn't do this...why? - Done
-
-- When a connection gets canceled we are leaving ssh processes behind - Done
-
-- Notify the user when a new host key is received. - Done
-
-- Warn the user when the host key changes. - Done
-
-- Add support for other versions of OpenSSH and SSH. - Done
-
-- Store passwords per user/host pair, not per connection
- (multiple users per host work now, but when switching views one has to
- re-enter the login info every single time) - Done! This was a problem with
- caching in the ioslave base class and has been fixed.
-
-- Do not move items to trash when deleting (probably a generic kio_slave
- problem, though, makes no sense to download everything you want to delete)
- - Done! Fixing in konqueror
-
-- Support for different ports (sftp://user@host:port), this can be done by
- adding the argument -oPort=7022 (or whatever port) to sftp, could not get
- it to work properly though - Done
+ Improve keyboard interactive authentication
+ Improve password dialog for passphrase
+ Support for statvfs with libssh 0.4
Index: runtime/kioslave/sftp/CMakeLists.txt
===================================================================
--- runtime/kioslave/sftp/CMakeLists.txt (revision 1026868)
+++ runtime/kioslave/sftp/CMakeLists.txt (working copy)
@@ -1,20 +1,13 @@
-add_subdirectory(tests)
-
########### next target ###############
-set(kio_sftp_PART_SRCS
- process.cpp
- atomicio.cpp
- kio_sftp.cpp
- sftpfileattr.cpp
- ksshprocess.cpp )
+set(kio_sftp_PART_SRCS
+ kio_sftp.cpp
+)
+include_directories(${LIBSSH_INCLUDE_DIRS})
kde4_add_plugin(kio_sftp ${kio_sftp_PART_SRCS})
-target_link_libraries(kio_sftp ${KDE4_KIO_LIBS} )
-if(NOT WIN32)
- target_link_libraries(kio_sftp ${KDE4_KPTY_LIBS})
-endif(NOT WIN32)
+target_link_libraries(kio_sftp ${KDE4_KIO_LIBS} ${LIBSSH_LIBRARIES})
install(TARGETS kio_sftp DESTINATION ${PLUGIN_INSTALL_DIR} )
Index: runtime/kioslave/CMakeLists.txt
===================================================================
--- runtime/kioslave/CMakeLists.txt (revision 1026868)
+++ runtime/kioslave/CMakeLists.txt (working copy)
@@ -7,6 +7,9 @@
macro_log_feature(SAMBA_FOUND "Samba" "the SMB client library, a version with smbc_set_context() and smbc_option_set()" "http://www.samba.org" FALSE "" "Needed to build the SMB kioslave")
endif(NOT WIN32)
+macro_optional_find_package(LibSSH 0.3.4)
+macro_log_feature(LIBSSH_FOUND "libssh" "the SSH library with SFTP support" "http://www.libssh.org/" FALSE "" "Needed to build the SFTP kioslave")
+
add_subdirectory( about )
add_subdirectory( bookmarks )
add_subdirectory( cgi )
@@ -21,18 +24,22 @@
endif(NOT WIN32)
add_subdirectory( remote )
add_subdirectory( desktop )
-add_subdirectory( sftp )
add_subdirectory( fish )
add_subdirectory( thumbnail )
add_subdirectory( docfilter )
+# msvc doesn't compile
+if (LIBSSH_FOUND AND NOT MSVC)
+ add_subdirectory(sftp)
+endif (LIBSSH_FOUND AND NOT MSVC)
if(NOT WIN32)
add_subdirectory( floppy )
add_subdirectory( finger )
add_subdirectory( man )
add_subdirectory( nfs )
+endif(NOT WIN32)
- if(SAMBA_FOUND)
- add_subdirectory(smb)
- endif(SAMBA_FOUND)
-endif(NOT WIN32)
+if(SAMBA_FOUND OR WIN32)
+ add_subdirectory(smb)
+endif(SAMBA_FOUND OR WIN32)
+