Sign Up
Log In
Log In
or
Sign Up
Places
All Projects
Status Monitor
Collapse sidebar
home:zhy20120210:failed_1
kdebase4-runtime
kio_sftp_rewrite.patch
Overview
Repositories
Revisions
Requests
Users
Attributes
Meta
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) +
Locations
Projects
Search
Status Monitor
Help
OpenBuildService.org
Documentation
API Documentation
Code of Conduct
Contact
Support
@OBShq
Terms
openSUSE Build Service is sponsored by
The Open Build Service is an
openSUSE project
.
Sign Up
Log In
Places
Places
All Projects
Status Monitor