diff --git a/tdeioslave/sftp/AUTHORS b/tdeioslave/sftp/AUTHORS deleted file mode 100644 index c763d00bc..000000000 --- a/tdeioslave/sftp/AUTHORS +++ /dev/null @@ -1,3 +0,0 @@ -Dawit Alemayehu -Lucas Fisher - diff --git a/tdeioslave/sftp/CHANGELOG b/tdeioslave/sftp/CHANGELOG deleted file mode 100644 index c34cd9dec..000000000 --- a/tdeioslave/sftp/CHANGELOG +++ /dev/null @@ -1,59 +0,0 @@ -- add dialog to ask for username -- rename() causes SSH to die -- How to handle overwrite? -- After the user cancels with the stop button, we get ERR_CANNOT_LAUNCH_PROCESS - errors, until we kill the ioslave. Same thing after trying the wrong passwd - too many times. - This is happening because TDEProcess thinks that the ssh process is still running - even though it exited. -- How to handle password and caching? - - Write our own askpass program using kde - - set env SSH_ASKPASS_PROGRAM before launching - -how to do this? TDEProcess doesn't give us access to env variables. - - Our askpass program can probably talk to the tdesu daemon to implement caching. -- chmod() succeeds, but konqueror always puts permissions to 0 afterwards. The properties - dialog is right though. - Nevermind - ftp ioslave does this too! Maybe a bug with konqueror. -- stat does not give us group and owner names, only numbers. We could cache the uid/name and - gid/name so we can give names when doing a stat also. - -7-13-2001 - ReadLink stopped working. sftp server always retuns a file not found error - - Need to implement 64 bit file lengths-->write DataStream << for u_int64 - Still need to offer 32 bit size since this is what kde wants. ljf - - rename() isn't exactly causing ioslave to die. The stat of the file we are - going to rename is killing the slave. The slave dies in the statEntry() call. - I don't know what I am putting in the UDS entry that is causing this. ljf -7-14-2001 - got put, mimetype working ljf - - fixed readlink problem - I was sending the wrong path. doh! ljf -7-17-2001 - If the user changes the host, the slave doesn't change host! setHost() is not - called, nor is another ioslave spawned. I have not investigated the problem - yet. ljf -7-21-2001 - got slave working with kde 2.2 cvs -7-22-2001 - probable solution to getting password prompt -- open with controlling - but don't connect stdin/out to terminal. duh! -8-9-2001 - Doh! I haven't kept very good logs. Look at the cvs logs for better info. - - At this point tdeio_sftp is using KSshProcess which I wrote in order to make - a standard interface to the various version of ssh out there. So far it is - working fairly well. We also now report host key changes to the user and - allow them to choose whether or not to continue. This is a big improvement. - - Todo: support use of keys and ssh agent - put()'s resume functionality needs some work -1-26-2002 - Rewrote put() following the ftp::put() so it should behave the same way - - increase the size of the data packet we ask for in ::get up to 60k. - Through-put increases nicely. - - Call closeConnection() from construction. Keeps from having unused ssh - processes laying around after failed operations. -2-19-2002 - get() now emits mimetype, fixes problem with konqi not downloading file for - viewing in kpart. - - get port number using getservbyname instead of hard coding it. -2-27-2002 - testing before committing back to cvs, test with openssh 3, ssh 3 -6-?-2002 - rewrote openConnection() to using new KSshProcess connect proceedures -7-20-2002 - Don't put up a message box when auth fails because of now or changed key, - the call to error() will put up the dialog. - - Connect fails and no more password are prompted for when we get - ERR_AUTH_FAILED from KSshProcess. -9-15-2002 - stuff -9-29-2002 - the last i18n string updates, fixed problem with uploading files to - openssh server. -5-8-2003 - check whether operation types are supported by the negotiated sftp - protocol version diff --git a/tdeioslave/sftp/CMakeL10n.txt b/tdeioslave/sftp/CMakeL10n.txt deleted file mode 100644 index f6ee3ab31..000000000 --- a/tdeioslave/sftp/CMakeL10n.txt +++ /dev/null @@ -1,3 +0,0 @@ -##### create translation templates ############## - -tde_l10n_create_template( "tdeio_sftp" ) diff --git a/tdeioslave/sftp/CMakeLists.txt b/tdeioslave/sftp/CMakeLists.txt index 62fe0fce3..8967d1ccd 100644 --- a/tdeioslave/sftp/CMakeLists.txt +++ b/tdeioslave/sftp/CMakeLists.txt @@ -1,13 +1,10 @@ -################################################# -# -# (C) 2010-2011 Serghei Amelian -# serghei (DOT) amelian (AT) gmail.com -# -# Improvements and feedback are welcome -# -# This file is released under GPL >= 2 -# -################################################# +########################################### +# # +# Improvements and feedback are welcome # +# # +# This file is released under GPL >= 2 # +# # +########################################### include_directories( ${CMAKE_CURRENT_BINARY_DIR} @@ -21,21 +18,21 @@ link_directories( ) -##### other data ################################ +##### other data ######################### tde_create_translated_desktop( SOURCE sftp.protocol DESTINATION ${SERVICES_INSTALL_DIR} - PO_DIR tdeioslave-desktops + #PO_DIR tdeioslave-desktops ) -##### tdeio_sftp (module) ######################### +##### tdeio_sftp (module) ################ set( target tdeio_sftp ) tde_add_kpart( ${target} AUTOMOC - SOURCES process.cpp atomicio.cpp tdeio_sftp.cpp sftpfileattr.cpp ksshprocess.cpp - LINK tdeio-shared tdesu-shared + SOURCES tdeio_sftp.cpp + LINK tdeio-shared DESTINATION ${PLUGIN_INSTALL_DIR} ) diff --git a/tdeioslave/sftp/DEBUGGING b/tdeioslave/sftp/DEBUGGING deleted file mode 100644 index 8e15c91d4..000000000 --- a/tdeioslave/sftp/DEBUGGING +++ /dev/null @@ -1,12 +0,0 @@ -DEBUGGING - -The best way to debug this slave is to send debug info to a -file using 'tdedebugDialog --fullmode'. Then you can 'tail -f' the file to -see debug messages in real-time. - -I also suggest getting the openssh source and recompiling sftp-server to -send messages to the auth log. This can be done in sftp-server.c be defining -DEBUG_SFTP_SERVER. - -You can do the same with the ssh client by finding the two calls to log_init() -in ssh.c and changing the last argument from 1 to 0 and recompiling. diff --git a/tdeioslave/sftp/Makefile.am b/tdeioslave/sftp/Makefile.am deleted file mode 100644 index 512f5f7dd..000000000 --- a/tdeioslave/sftp/Makefile.am +++ /dev/null @@ -1,25 +0,0 @@ -## Makefile.am of tdebase/tdeioslave/sftp - -INCLUDES = $(all_includes) -AM_LDFLAGS = $(all_libraries) $(KDE_RPATH) $(LIB_TQT) -lDCOP $(LIB_TDECORE) $(LIB_TDEUI) -ltdefx $(LIB_TDEIO) -ltdetexteditor -METASOURCES = AUTO - -####### Files - -check_PROGRAMS = ksshprocesstest - -ksshprocesstest_SOURCES = ksshprocesstest.cpp -ksshprocesstest_LDADD = $(LIB_TDESYCOCA) ksshprocess.lo process.lo atomicio.lo - -kde_module_LTLIBRARIES = tdeio_sftp.la - -tdeio_sftp_la_SOURCES = process.cpp atomicio.cpp tdeio_sftp.cpp sftpfileattr.cpp ksshprocess.cpp -tdeio_sftp_la_LIBADD = $(LIB_TDEIO) -tdeio_sftp_la_LDFLAGS = $(all_libraries) -module $(KDE_PLUGIN) -noinst_HEADERS = atomicio.h tdeio_sftp.h ksshprocess.h process.h sftpfileattr.h sftp.h - -kdelnk_DATA = sftp.protocol -kdelnkdir = $(kde_servicesdir) - -messages: - $(XGETTEXT) *.cpp -o $(podir)/tdeio_sftp.pot diff --git a/tdeioslave/sftp/TODO b/tdeioslave/sftp/TODO deleted file mode 100644 index 0f1411317..000000000 --- a/tdeioslave/sftp/TODO +++ /dev/null @@ -1,5 +0,0 @@ -TODO: -===== - -- Support for use of public keys, maybe ssh-agent, a key management app, etc. - diff --git a/tdeioslave/sftp/atomicio.cpp b/tdeioslave/sftp/atomicio.cpp deleted file mode 100644 index 057f20fe9..000000000 --- a/tdeioslave/sftp/atomicio.cpp +++ /dev/null @@ -1,67 +0,0 @@ -/* - * Copyright (c) 1995,1999 Theo de Raadt. All rights reserved. - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions - * are met: - * 1. Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * 2. Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR - * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES - * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. - * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, - * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT - * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, - * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY - * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF - * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -//#include "includes.h" -//RCSID("$OpenBSD: atomicio.c,v 1.9 2001/03/02 18:54:30 deraadt Exp $"); - -//#include "xmalloc.h" -#include "atomicio.h" -#include -#include -#include - -/* - * ensure all of data on socket comes through. f==read || f==write - */ - -ssize_t atomicio(int fd, char *_s, size_t n, bool read) -{ - char *s = _s; - ssize_t res; - ssize_t pos = 0; - - while (n > pos) { - if( read) - res = ::read(fd, s + pos, n - pos); - else - res = ::write(fd, s + pos, n - pos); - - switch (res) { - case -1: - kdDebug() << "atomicio(): errno=" << errno << endl; -#ifdef EWOULDBLOCK - if (errno == EINTR || errno == EAGAIN || errno == EWOULDBLOCK) -#else - if (errno == EINTR || errno == EAGAIN) -#endif - continue; - case 0: - return (res); - default: - pos += res; - } - } - return (pos); -} diff --git a/tdeioslave/sftp/atomicio.h b/tdeioslave/sftp/atomicio.h deleted file mode 100644 index 4468757d5..000000000 --- a/tdeioslave/sftp/atomicio.h +++ /dev/null @@ -1,39 +0,0 @@ -#ifndef atomicio_h -#define atomicio_h - -/* $OpenBSD: atomicio.h,v 1.3 2001/03/02 18:54:30 deraadt Exp $ */ - -/* - * Copyright (c) 1995,1999 Theo de Raadt. All rights reserved. - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions - * are met: - * 1. Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * 2. Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR - * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES - * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. - * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, - * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT - * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, - * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY - * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF - * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -#include -#include - -/* - * Ensure all of data on socket comes through. f==read || f==write - */ -ssize_t atomicio(int fd, char *_s, size_t n, bool read = true); - -#endif diff --git a/tdeioslave/sftp/ksshprocess.cpp b/tdeioslave/sftp/ksshprocess.cpp deleted file mode 100644 index 0f19126d2..000000000 --- a/tdeioslave/sftp/ksshprocess.cpp +++ /dev/null @@ -1,1114 +0,0 @@ -/*************************************************************************** - ksshprocess.cpp - description - ------------------- - begin : Tue Jul 31 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. * - * * - ***************************************************************************/ - -/* - * See the KSshProcess header for examples on use. - * - * This class uses a hacked version of the PTYProcess - * class. This was needed because the tdelibs PTYProcess does not provide - * access to the pty file descriptor which we need, because ssh prints the - * password prompt to the pty and reads the password from the pty. I don't - * feel I know enough about ptys to confidently modify the orignial - * PTYProcess class. - * - * To start ssh we take the arguments the user gave us - * in the SshOptList and build the ssh command arguments based on the version - * of ssh we are using. This command and its arguments are passed to - * PTYProcess for execution. Once ssh is started we scan each line of input - * from stdin, stderr, and the pty for recognizable strings. The recognizable - * strings are taken from several string tables. Each table contains a string - * for each specific version of ssh we support and a string for a generic - * version of OpenSSH and commercial SSH incase we don't recognized the - * specific ssh version strings (as when a new SSH version is released after - * a release of KSshProcess). There are tables for ssh version strings, - * password prompts, new host key errors, different host key errors, - * messages than indicate a successful connect, authentication errors, etc. - * If we find user interaction is necessary, for instance to provide a - * password or passphrase, we return a err code to the user who can send - * a message to KSshProcess, using one of several methods, to correct - * the error. - * - * Determining when the ssh connection has successfully authenticationed has - * proved to be the most difficult challenge. OpenSSH does not print a message - * on successful authentication, thus the only way to know is to send data - * and wait for a return. The problem here is sometimes it can take a bit - * to establish the connection (for example, do to DNS lookups). This means - * the user may be sitting there waiting for a connection that failed. - * Instead, ssh is always started with the verbose flag. Then we look for - * a message that indicates auth succeeded. This is hazardous because - * debug messages are more likely to change between OpenSSH releases. - * Thus, we could become incompatible with new OpenSSH releases. - */ - -#include - -#include "ksshprocess.h" - -#include -#include - -#ifdef HAVE_SYS_TIME_H -#include -#endif - -#include -#include -#include - -/* - * The following are tables of string and regexps we match - * against the output of ssh. An entry in each array - * corresponds the the version of ssh found in versionStrs[]. - * - * The version strings must be ordered in the array from most - * specific to least specific in cases where the beginning - * of several version strings are the similar. For example, - * consider the openssh version strings. The generic "OpenSSH" - * must be the last of the openssh version strings in the array - * so that is matched last. We use these generic version strings - * so we can do a best effor to support unknown ssh versions. - */ -TQRegExp KSshProcess::versionStrs[] = { - TQRegExp("OpenSSH_3\\.[6-9]|OpenSSH_[1-9]*[4-9]\\.[0-9]"), - TQRegExp("OpenSSH"), - TQRegExp("SSH Secure Shell") -}; - -const char * const KSshProcess::passwordPrompt[] = { - "password:", // OpenSSH - "password:", // OpenSSH - "password:" // SSH -}; - -const char * const KSshProcess::passphrasePrompt[] = { - "Enter passphrase for key", - "Enter passphrase for key", - "Passphrase for key" -}; - -const char * const KSshProcess::authSuccessMsg[] = { - "Authentication succeeded", - "ssh-userauth2 successful", - "Received SSH_CROSS_AUTHENTICATED packet" -}; - -const char* const KSshProcess::authFailedMsg[] = { - "Permission denied (", - "Permission denied (", - "Authentication failed." -}; - -const char* const KSshProcess::tryAgainMsg[] = { - "please try again", - "please try again", - "adjfhjsdhfdsjfsjdfhuefeufeuefe" -}; - -TQRegExp KSshProcess::hostKeyMissingMsg[] = { - TQRegExp("The authenticity of host|No (DSA|RSA) host key is known for"), - TQRegExp("The authenticity of host|No (DSA|RSA) host key is known for"), - TQRegExp("Host key not found from database") -}; - -const char* const KSshProcess::continuePrompt[] = { - "Are you sure you want to continue connecting (yes/no)?", - "Are you sure you want to continue connecting (yes/no)?", - "Are you sure you want to continue connecting (yes/no)?" -}; - -const char* const KSshProcess::hostKeyChangedMsg[] = { - "WARNING: REMOTE HOST IDENTIFICATION HAS CHANGED!", - "WARNING: REMOTE HOST IDENTIFICATION HAS CHANGED!", - "WARNING: HOST IDENTIFICATION HAS CHANGED!" -}; - -TQRegExp KSshProcess::keyFingerprintMsg[] = { - TQRegExp("..(:..){15}"), - TQRegExp("..(:..){15}"), - TQRegExp(".....(-.....){10}") -}; - -TQRegExp KSshProcess::knownHostsFileMsg[] = { - TQRegExp("Add correct host key in (.*) to get rid of this message."), - TQRegExp("Add correct host key in (.*) to get rid of this message."), - TQRegExp("Add correct host key to \"(.*)\"") -}; - - -// This prompt only applies to commerical ssh. -const char* const KSshProcess::changeHostKeyOnDiskPrompt[] = { - "as;jf;sajkfdslkfjas;dfjdsa;fj;dsajfdsajf", - "as;jf;sajkfdslkfjas;dfjdsa;fj;dsajfdsajf", - "Do you want to change the host key on disk (yes/no)?" -}; - -// We need this in addition the authFailedMsg because when -// OpenSSH gets a changed host key it will fail to connect -// depending on the StrictHostKeyChecking option. Depending -// how this option is set, it will print "Permission denied" -// and quit, or print "Host key verification failed." and -// quit. The later if StrictHostKeyChecking is "no". -// The former if StrictHostKeyChecking is -// "yes" or explicitly set to "ask". -TQRegExp KSshProcess::hostKeyVerifyFailedMsg[] = { - TQRegExp("Host key verification failed\\."), - TQRegExp("Host key verification failed\\."), - TQRegExp("Disconnected; key exchange or algorithm? negotiation failed \\(Key exchange failed\\.\\)\\.") -}; - -const char * const KSshProcess::connectionClosedMsg[] = { - "Connection closed by remote host", - "Connection closed by remote host", - "Connection closed by remote host" -}; - - -void KSshProcess::SIGCHLD_handler(int) { - while(waitpid(-1, NULL, WNOHANG) > 0); -} - -void KSshProcess::installSignalHandlers() { - struct sigaction act; - memset(&act,0,sizeof(act)); - act.sa_handler = SIGCHLD_handler; - act.sa_flags = 0 -#ifdef SA_NOCLDSTOP - | SA_NOCLDSTOP -#endif -#ifdef SA_RESTART - | SA_RESTART -#endif - ; - sigaction(SIGCHLD,&act,NULL); -} - -void KSshProcess::removeSignalHandlers() { - struct sigaction act; - memset(&act,0,sizeof(act)); - act.sa_handler = SIG_DFL; - sigaction(SIGCHLD,&act,NULL); -} - -KSshProcess::KSshProcess() - : mVersion(UNKNOWN_VER), mConnected(false), - mRunning(false), mConnectState(0) { - mSshPath = TDEStandardDirs::findExe(TQString::fromLatin1("ssh")); - kdDebug(KSSHPROC) << "KSshProcess::KSshProcess(): ssh path [" << - mSshPath << "]" << endl; - - installSignalHandlers(); -} - -KSshProcess::KSshProcess(TQString pathToSsh) - : mSshPath(pathToSsh), mVersion(UNKNOWN_VER), mConnected(false), - mRunning(false), mConnectState(0) { - installSignalHandlers(); -} - -KSshProcess::~KSshProcess(){ - disconnect(); - removeSignalHandlers(); - while(waitpid(-1, NULL, WNOHANG) > 0); -} - -bool KSshProcess::setSshPath(TQString pathToSsh) { - mSshPath = pathToSsh; - version(); - if( mVersion == UNKNOWN_VER ) - return false; - - return true; -} - -KSshProcess::SshVersion KSshProcess::version() { - TQString cmd; - cmd = mSshPath+" -V 2>&1"; - - // Get version string from ssh client. - FILE *p; - if( (p = popen(cmd.latin1(), "r")) == NULL ) { - kdDebug(KSSHPROC) << "KSshProcess::version(): " - "failed to start ssh: " << strerror(errno) << endl; - return UNKNOWN_VER; - } - - // Determine of the version from the version string. - size_t len; - char buf[128]; - if( (len = fread(buf, sizeof(char), sizeof(buf)-1, p)) == 0 ) { - kdDebug(KSSHPROC) << "KSshProcess::version(): " - "Read of ssh version string failed " << - strerror(ferror(p)) << endl; - return UNKNOWN_VER; - } - if( pclose(p) == -1 ) { - kdError(KSSHPROC) << "KSshProcess::version(): pclose failed." << endl; - } - buf[len] = '\0'; - TQString ver; - ver = buf; - kdDebug(KSSHPROC) << "KSshProcess::version(): " - "got version string [" << ver << "]" << endl; - - mVersion = UNKNOWN_VER; - for(int i = 0; i < SSH_VER_MAX; i++) { - if( ver.find(versionStrs[i]) != -1 ) { - mVersion = (SshVersion)i; - break; - } - } - - kdDebug(KSSHPROC) << "KSshPRocess::version(): version number = " - << mVersion << endl; - - if( mVersion == UNKNOWN_VER ) { - kdDebug(KSSHPROC) << "KSshProcess::version(): " - "Sorry, I don't know about this version of ssh" << endl; - mError = ERR_UNKNOWN_VERSION; - return UNKNOWN_VER; - } - - return mVersion; -} -/* -TQString KSshProcess::versionStr() { - if( mVersion == UNKNOWN_VER ) { - version(); - if( mVersion == UNKNOWN_VER ) - return TQString::null; - } - - return TQString::fromLatin1(versionStrs[mVersion]); -} -*/ - -bool KSshProcess::setOptions(const SshOptList& opts) { - kdDebug(KSSHPROC) << "KSshProcess::setOptions()" << endl; - mArgs.clear(); - SshOptListConstIterator it; - TQString cmd, subsystem; - mPassword = mUsername = mHost = TQString::null; - TQCString tmp; - for(it = opts.begin(); it != opts.end(); ++it) { - //kdDebug(KSSHPROC) << "opt.opt = " << (*it).opt << endl; - //kdDebug(KSSHPROC) << "opt.str = " << (*it).str << endl; - //kdDebug(KSSHPROC) << "opt.num = " << (*it).num << endl; - switch( (*it).opt ) { - case SSH_VERBOSE: - mArgs.append("-v"); - break; - - case SSH_SUBSYSTEM: - subsystem = (*it).str; - break; - - case SSH_PORT: - mArgs.append("-p"); - tmp.setNum((*it).num); - mArgs.append(tmp); - mPort = (*it).num; - break; - - case SSH_HOST: - mHost = (*it).str; - break; - - case SSH_USERNAME: - mArgs.append("-l"); - mArgs.append((*it).str.latin1()); - mUsername = (*it).str; - break; - - case SSH_PASSWD: - mPassword = (*it).str; - break; - - case SSH_PROTOCOL: - if( mVersion <= OPENSSH ) { - tmp = "Protocol="; - tmp += TQString::number((*it).num).latin1(); - mArgs.append("-o"); - mArgs.append(tmp); - } - else if( mVersion <= SSH ) { - if( (*it).num == 1 ) { - mArgs.append("-1"); - } - // else uses version 2 by default - } - break; - - case SSH_FORWARDX11: - tmp = "ForwardX11="; - tmp += (*it).boolean ? "yes" : "no"; - mArgs.append("-o"); - mArgs.append(tmp); - break; - - case SSH_FORWARDAGENT: - tmp = "ForwardAgent="; - tmp += (*it).boolean ? "yes" : "no"; - mArgs.append("-o"); - mArgs.append(tmp); - break; - - case SSH_ESCAPE_CHAR: - if( (*it).num == -1 ) - tmp = "none"; - else - tmp = (char)((*it).num); - mArgs.append("-e"); - mArgs.append(tmp); - break; - - case SSH_OPTION: - // don't allow NumberOfPasswordPrompts or StrictHostKeyChecking - // since KSshProcess depends on specific setting of these for - // preforming authentication correctly. - tmp = (*it).str.latin1(); - if( tmp.contains("NumberOfPasswordPrompts") || - tmp.contains("StrictHostKeyChecking") ) { - mError = ERR_INVALID_OPT; - return false; - } - else { - mArgs.append("-o"); - mArgs.append(tmp); - } - break; - - case SSH_COMMAND: - cmd = (*it).str; - break; - - default: - kdDebug(KSSHPROC) << "KSshProcess::setOptions(): " - "unrecognized ssh opt " << (*it).opt << endl; - } - } - - if( !subsystem.isEmpty() && !cmd.isEmpty() ) { - kdDebug(KSSHPROC) << "KSshProcess::setOptions(): " - "cannot use a subsystem and command at the same time" << endl; - mError = ERR_CMD_SUBSYS_CONFLICT; - mErrorMsg = i18n("Cannot specify a subsystem and command at the same time."); - return false; - } - - // These options govern the behavior of ssh and - // cannot be defined by the user - //mArgs.append("-o"); - //mArgs.append("StrictHostKeyChecking=ask"); - mArgs.append("-v"); // So we get a message that the - // connection was successful - if( mVersion <= OPENSSH ) { - // nothing - } - else if( mVersion <= SSH ) { - mArgs.append("-o"); // So we can check if the connection was successful - mArgs.append("AuthenticationSuccessMsg=yes"); - } - - if( mHost.isEmpty() ) { - kdDebug(KSSHPROC) << "KSshProcess::setOptions(): " - "a host name must be supplied" << endl; - return false; - } - else { - mArgs.append(mHost.latin1()); - } - - if( !subsystem.isEmpty() ) { - mArgs.append("-s"); - mArgs.append(subsystem.latin1()); - } - - if( !cmd.isEmpty() ) { - mArgs.append(cmd.latin1()); - } - - return true; -} - -void KSshProcess::printArgs() { - TQValueListIterator it; - for( it = mArgs.begin(); it != mArgs.end(); ++it) { - kdDebug(KSSHPROC) << "arg: " << *it << endl; - } -} - - -int KSshProcess::error(TQString& msg) { - kdDebug(KSSHPROC) << "KSshProcess::error()" << endl; - kdDebug() << mErrorMsg << endl; - msg = mErrorMsg; - return mError; -} - -void KSshProcess::kill(int signal) { - int pid = ssh.pid(); - - kdDebug(KSSHPROC) << "KSshProcess::kill(signal:" << signal - << "): ssh pid is " << pid << endl; - kdDebug(KSSHPROC) << "KSshPRocess::kill(): we are " - << (mConnected ? "" : "not ") << "connected" << endl; - kdDebug(KSSHPROC) << "KSshProcess::kill(): we are " - << (mRunning ? "" : "not ") << "running a ssh process" << endl; - - if( mRunning && pid > 1 ) { - // Kill the child process... - if ( ::kill(pid, signal) == 0 ) { - // clean up if we tried to kill the process - if( signal == SIGTERM || signal == SIGKILL ) { - while(waitpid(-1, NULL, WNOHANG) > 0); - mConnected = false; - mRunning = false; - } - } - else - kdDebug(KSSHPROC) << "KSshProcess::kill(): kill failed" << endl; - } - else - kdDebug(KSSHPROC) << "KSshProcess::kill(): " - "Refusing to kill ssh process" << endl; -} - - - -/** - * Try to open an ssh connection. - * SSH prints certain messages to certain file descriptiors: - * passwordPrompt - pty - * passphrasePrompt - pty - * authSuccessMsg - stderr (OpenSSH), - * authFailedMsg - stderr - * hostKeyMissing - stderr - * hostKeyChanged - stderr - * continuePrompt - stderr - * - * We will use a select to wait for a line on each descriptor. Then get - * each line that available and take action based on it. The type - * of messages we are looking for and the action we take on each - * message are: - * passwordPrompt - Return false, set error to ERR_NEED_PASSWD. - * On the next call to connect() we expect a password - * to be available. - * - * passpharsePrompt - Return false, set error to ERR_NEED_PASSPHRASE. - * On the next call to connect() we expect a - * passphrase to be available. - * - * authSuccessMsg - Return true, as we have successfully established a - * ssh connection. - * - * authFailedMsg - Return false, set error to ERR_AUTH_FAILED. We - * were unable to authenticate the connection given - * the available authentication information. - * - * hostKeyMissing - Return false, set error to ERR_NEW_HOST_KEY. Caller - * must call KSshProcess.acceptHostKey(bool) to accept - * or reject the key before calling connect() again. - * - * hostKeyChanged - Return false, set error to ERR_DIFF_HOST_KEY. Caller - * must call KSshProcess.acceptHostKey(bool) to accept - * or reject the key before calling connect() again. - * - * continuePrompt - Send 'yes' or 'no' to accept or reject a key, - * respectively. - * - */ - - -void KSshProcess::acceptHostKey(bool accept) { - kdDebug(KSSHPROC) << "KSshProcess::acceptHostKey(accept:" - << accept << ")" << endl; - mAcceptHostKey = accept; -} - -void KSshProcess::setPassword(TQString password) { - kdDebug(KSSHPROC) << "KSshProcess::setPassword(password:xxxxxxxx)" << endl; - mPassword = password; -} - -TQString KSshProcess::getLine() { - static TQStringList buffer; - TQString line = TQString::null; - TQCString ptyLine, errLine; - - if( buffer.empty() ) { - // PtyProcess buffers lines. First check that there - // isn't something on the PtyProces buffer or that there - // is not data ready to be read from the pty or stderr. - ptyLine = ssh.readLineFromPty(false); - errLine = ssh.readLineFromStderr(false); - - // If PtyProcess did have something for us, get it and - // place it in our line buffer. - if( ! ptyLine.isEmpty() ) { - buffer.prepend(TQString(ptyLine)); - } - - if( ! errLine.isEmpty() ) { - buffer.prepend(TQString(errLine)); - } - - // If we still don't have anything in our buffer so there must - // not be anything on the pty or stderr. Setup a select() - // to wait for some data from SSH. - // Hack around select() failure on newer systems - unsigned long milliseconds = 0; - while ((buffer.size() == 0) && (milliseconds < (60*1000))) { - //kdDebug(KSSHPROC) << "KSshProcess::getLine(): " << - // "Line buffer empty, calling select() to wait for data." << endl; - int errfd = ssh.stderrFd(); - int ptyfd = ssh.fd(); - fd_set rfds; - fd_set efds; - struct timeval tv; - - // find max file descriptor - int maxfd = ptyfd > errfd ? ptyfd : errfd; - - FD_ZERO(&rfds); - FD_SET(ptyfd, &rfds); // Add pty file descriptor - FD_SET(errfd, &rfds); // Add std error file descriptor - - FD_ZERO(&efds); - FD_SET(ptyfd, &efds); - FD_SET(errfd, &efds); - - tv.tv_sec = 60; tv.tv_usec = 0; // 60 second timeout - - // Wait for a message from ssh on stderr or the pty. - int ret = -1; - do - ret = ::select(maxfd+1, &rfds, NULL, &efds, &tv); - while( ret == -1 && errno == EINTR ); - - // Handle any errors from select - if( ret == 0 ) { - kdDebug(KSSHPROC) << "KSshProcess::connect(): " << - "timed out waiting for a response" << endl; - mError = ERR_TIMED_OUT; - return TQString::null; - } - else if( ret == -1 ) { - kdDebug(KSSHPROC) << "KSshProcess::connect(): " - << "select error: " << strerror(errno) << endl; - mError = ERR_INTERNAL; - return TQString::null; - } - - // We are not respecting any type of order in which the - // lines were received. Who knows whether pty or stderr - // had data on it first. - if( FD_ISSET(ptyfd, &rfds) ) { - ptyLine = ssh.readLineFromPty(false); - if (ptyLine.size() > 0) { - buffer.prepend(TQString(ptyLine)); - } - //kdDebug(KSSHPROC) << "KSshProcess::getLine(): " - // "line from pty -" << ptyLine << endl; - } - - if( FD_ISSET(errfd, &rfds) ) { - errLine = ssh.readLineFromStderr(false); - if (errLine.size() > 0) { - buffer.prepend(TQString(errLine)); - } - //kdDebug(KSSHPROC) << "KSshProcess::getLine(): " - // "line from err -" << errLine << endl; - } - - if( FD_ISSET(ptyfd, &efds) ) { - kdDebug(KSSHPROC) << "KSshProcess::getLine(): " - "Exception on pty file descriptor." << endl; - } - - if( FD_ISSET(errfd, &efds) ) { - kdDebug(KSSHPROC) << "KSshProcess::getLine(): " - "Exception on std err file descriptor." << endl; - } - - if (buffer.size() == 0) { - milliseconds++; - usleep(1000); - } - } - } - - // We should have something in our buffer now. - // Return the last line. - //it = buffer.end(); - //line = *it; - //buffer.remove(it); - - line = buffer.last(); - buffer.pop_back(); - - if( line.isNull() && buffer.count() > 0 ) { - line = buffer.last(); - buffer.pop_back(); - } - -// kdDebug(KSSHPROC) << "KSshProcess::getLine(): " << -// buffer.count() << " lines in buffer" << endl; - kdDebug(KSSHPROC) << "KSshProcess::getLine(): " - "ssh: " << line << endl; - - - return line; -} - -// All the different states we could go through while trying to connect. -enum sshConnectState { - STATE_START, STATE_TRY_PASSWD, STATE_WAIT_PROMPT, STATE_NEW_KEY_CONTINUE, - STATE_DIFF_KEY_CONTINUE, STATE_FATAL, STATE_WAIT_CONTINUE_PROMPT, - STATE_SEND_CONTINUE, STATE_AUTH_FAILED, STATE_NEW_KEY_WAIT_CONTINUE, - STATE_DIFF_KEY_WAIT_CONTINUE, STATE_TRY_PASSPHRASE -}; - -// Print the state as a string. Good for debugging -const char* stateStr(int state) { - switch(state) { - case STATE_START: - return "STATE_START"; - case STATE_TRY_PASSWD: - return "STATE_TRY_PASSWD"; - case STATE_WAIT_PROMPT: - return "STATE_WAIT_PROMPT"; - case STATE_NEW_KEY_CONTINUE: - return "STATE_NEW_KEY_CONTINUE"; - case STATE_DIFF_KEY_CONTINUE: - return "STATE_DIFF_KEY_CONTINUE"; - case STATE_FATAL: - return "STATE_FATAL"; - case STATE_WAIT_CONTINUE_PROMPT: - return "STATE_WAIT_CONTINUE_PROMPT"; - case STATE_SEND_CONTINUE: - return "STATE_SEND_CONTINE"; - case STATE_AUTH_FAILED: - return "STATE_AUTH_FAILED"; - case STATE_NEW_KEY_WAIT_CONTINUE: - return "STATE_NEW_KEY_WAIT_CONTINUE"; - case STATE_DIFF_KEY_WAIT_CONTINUE: - return "STATE_DIFF_KEY_WAIT_CONTINUE"; - case STATE_TRY_PASSPHRASE: - return "STATE_TRY_PASSPHRASE"; - } - return "UNKNOWN"; -} - -bool KSshProcess::connect() { - if( mVersion == UNKNOWN_VER ) { - // we don't know the ssh version yet, so find out - version(); - if( mVersion == -1 ) { - return false; - } - } - - // We'll put a limit on the number of state transitions - // to ensure we don't go out of control. - int transitionLimit = 500; - - while(--transitionLimit) { - kdDebug(KSSHPROC) << "KSshProcess::connect(): " - << "Connect state " << stateStr(mConnectState) << endl; - - TQString line; // a line from ssh - TQString msgBuf; // buffer for important messages from ssh - // which are to be returned to the user - - switch(mConnectState) { - // STATE_START: - // Executes the ssh binary with the options provided. If no options - // have been specified, sets error and returns false. Continue to - // state 1 if execution is successful, otherwise set error and - // return false. - case STATE_START: - // reset some key values to safe values - mAcceptHostKey = false; - mKeyFingerprint = TQString::null; - mKnownHostsFile = TQString::null; - - if( mArgs.isEmpty() ) { - kdDebug(KSSHPROC) << "KSshProcess::connect(): ssh options " - "need to be set first using setArgs()" << endl; - mError = ERR_NO_OPTIONS; - mErrorMsg = i18n("No options provided for ssh execution."); - return false; - } - - if( ssh.exec(mSshPath.latin1(), mArgs) ) { - kdDebug(KSSHPROC) << - "KSshProcess::connect(): ssh exec failed" << endl; - mError = ERR_CANNOT_LAUNCH; - mErrorMsg = i18n("Failed to execute ssh process."); - return false; - } - - kdDebug(KSSHPROC) << "KSshPRocess::connect(): ssh pid = " << ssh.pid() << endl; - - // set flag to indicate what have started a ssh process - mRunning = true; - mConnectState = STATE_WAIT_PROMPT; - break; - - // STATE_WAIT_PROMPT: - // Get a line of input from the ssh process. Check the contents - // of the line to determine the next state. Ignore the line - // if we don't recognize its contents. If the line contains - // the continue prompt, we have an error since we should never - // get that line in this state. Set ERR_INVALID_STATE error - // and return false. - case STATE_WAIT_PROMPT: - line = getLine(); - if( line.isNull() ) { - kdDebug(KSSHPROC) << "KSshProcess::connect(): " - "Got null line in STATE_WAIT_PROMPT." << endl; - mError = ERR_INTERACT; - mErrorMsg = - i18n("Error encountered while talking to ssh."); - mConnectState = STATE_FATAL; - } - else if( line.find(TQString::fromLatin1(passwordPrompt[mVersion]), 0, false) != -1 ) { - mConnectState = STATE_TRY_PASSWD; - } - else if( line.find(passphrasePrompt[mVersion]) != -1 ) { - mConnectState = STATE_TRY_PASSPHRASE; - } - else if( line.find(authSuccessMsg[mVersion]) != -1 ) { - return true; - } - else if( line.find(authFailedMsg[mVersion]) != -1 - && line.find(tryAgainMsg[mVersion]) == -1 ) { - mConnectState = STATE_AUTH_FAILED; - } - else if( line.find(hostKeyMissingMsg[mVersion]) != -1 ) { - mConnectState = STATE_NEW_KEY_WAIT_CONTINUE; - } - else if( line.find(hostKeyChangedMsg[mVersion]) != -1 ) { - mConnectState = STATE_DIFF_KEY_WAIT_CONTINUE; - } - else if( line.find(continuePrompt[mVersion]) != -1 ) { - //mConnectState = STATE_SEND_CONTINUE; - kdDebug(KSSHPROC) << "KSshProcess:connect(): " - "Got continue prompt where we shouldn't (STATE_WAIT_PROMPT)" - << endl; - mError = ERR_INTERACT; - mErrorMsg = - i18n("Error encountered while talking to ssh."); - } - else if( line.find(connectionClosedMsg[mVersion]) != -1 ) { - mConnectState = STATE_FATAL; - mError = ERR_CLOSED_BY_REMOTE_HOST; - mErrorMsg = i18n("Connection closed by remote host."); - } - else if( line.find(changeHostKeyOnDiskPrompt[mVersion]) != -1 ) { - // always say yes to this. It always comes after commerical ssh - // prints a "continue to connect prompt". We assume that if the - // user choose to continue, then they also want to save the - // host key to disk. - ssh.writeLine("yes"); - } - else { - // ignore line - } - break; - - // STATE_TRY_PASSWD: - // If we have password send it to the ssh process, else - // set error ERR_NEED_PASSWD and return false to the caller. - // The caller then must then call KSshProcess::setPassword(TQString) - // before calling KSshProcess::connect() again. - // - // Almost exactly liek STATE_TRY_PASSPHRASE. Check there if you - // make changes here. - case STATE_TRY_PASSWD: - // We have a password prompt waiting for us to supply - // a password. Send that password to ssh. If the caller - // did not supply a password like we asked, then ask - // again. - if( !mPassword.isEmpty() ) { -// ssh.WaitSlave(); - ssh.writeLine(mPassword.latin1()); - - // Overwrite the password so it isn't in memory. - mPassword.fill(TQChar('X')); - - // Set the password to null so we will request another - // password if this one fails. - mPassword = TQString::null; - - mConnectState = STATE_WAIT_PROMPT; - } - else { - kdDebug(KSSHPROC) << "KSshProcess::connect() " - "Need password from caller." << endl; - // The caller needs to supply a password before - // connecting can continue. - mError = ERR_NEED_PASSWD; - mErrorMsg = i18n("Please supply a password."); - mConnectState = STATE_TRY_PASSWD; - return false; - } - break; - - // STATE_TRY_KEY_PASSPHRASE: - // If we have passphrase send it to the ssh process, else - // set error ERR_NEED_PASSPHRASE and return false to the caller. - // The caller then must then call KSshProcess::setPassword(TQString) - // before calling KSshProcess::connect() again. - // - // Almost exactly like STATE_TRY_PASSWD. The only difference is - // the error we set if we don't have a passphrase. We duplicate - // this code to keep in the spirit of the state machine. - case STATE_TRY_PASSPHRASE: - // We have a passphrase prompt waiting for us to supply - // a passphrase. Send that passphrase to ssh. If the caller - // did not supply a passphrase like we asked, then ask - // again. - if( !mPassword.isEmpty() ) { -// ssh.WaitSlave(); - ssh.writeLine(mPassword.latin1()); - - // Overwrite the password so it isn't in memory. - mPassword.fill(TQChar('X')); - - // Set the password to null so we will request another - // password if this one fails. - mPassword = TQString::null; - - mConnectState = STATE_WAIT_PROMPT; - } - else { - kdDebug(KSSHPROC) << "KSshProcess::connect() " - "Need passphrase from caller." << endl; - // The caller needs to supply a passphrase before - // connecting can continue. - mError = ERR_NEED_PASSPHRASE; - mErrorMsg = i18n("Please supply the passphrase for " - "your SSH private key."); - mConnectState = STATE_TRY_PASSPHRASE; - return false; - } - break; - - // STATE_AUTH_FAILED: - // Authentication has failed. Tell the caller by setting the - // ERR_AUTH_FAILED error and returning false. If - // auth has failed then ssh should have exited, but - // we will kill it to make sure. - case STATE_AUTH_FAILED: - mError = ERR_AUTH_FAILED; - mErrorMsg = i18n("Authentication to %1 failed").arg(mHost); - mConnectState = STATE_FATAL; - break; - - // STATE_NEW_KEY_WAIT_CONTINUE: - // Grab lines from ssh until we get a continue prompt or a auth - // denied. We will get the later if StrictHostKeyChecking is set - // to yes. Go to STATE_NEW_KEY_CONTINUE if we get a continue prompt. - case STATE_NEW_KEY_WAIT_CONTINUE: - line = getLine(); - if( line.isNull() ) { - kdDebug(KSSHPROC) << "KSshProcess::connect(): " - "Got null line in STATE_NEW_KEY_WAIT_CONTINUE." << endl; - mError = ERR_INTERACT; - mErrorMsg = - i18n("Error encountered while talking to ssh."); - mConnectState = STATE_FATAL; - } - else if( (line.find(authFailedMsg[mVersion]) != -1 - && line.find(tryAgainMsg[mVersion]) == -1) - || line.find(hostKeyVerifyFailedMsg[mVersion]) != -1 ) { - mError = ERR_AUTH_FAILED_NEW_KEY; - mErrorMsg = i18n( - "The identity of the remote host '%1' could not be verified " - "because the host's key is not in the \"known hosts\" file." - ).arg(mHost); - - if( mKnownHostsFile.isEmpty() ) { - mErrorMsg += i18n( - " Manually, add the host's key to the \"known hosts\" " - "file or contact your administrator." - ); - } - else { - mErrorMsg += i18n( - " Manually, add the host's key to %1 " - "or contact your administrator." - ).arg(mKnownHostsFile); - } - - mConnectState = STATE_FATAL; - } - else if( line.find(continuePrompt[mVersion]) != -1 ) { - mConnectState = STATE_NEW_KEY_CONTINUE; - } - else if( line.find(connectionClosedMsg[mVersion]) != -1 ) { - mConnectState = STATE_FATAL; - mError = ERR_CLOSED_BY_REMOTE_HOST; - mErrorMsg = i18n("Connection closed by remote host."); - } - else if( line.find(keyFingerprintMsg[mVersion]) != -1 ) { - mKeyFingerprint = keyFingerprintMsg[mVersion].cap(); - kdDebug(KSSHPROC) << "Found key fingerprint: " << mKeyFingerprint << endl; - mConnectState = STATE_NEW_KEY_WAIT_CONTINUE; - } - else { - // ignore line - } - break; - - - // STATE_NEW_KEY_CONTINUE: - // We got a continue prompt for the new key message. Set the error - // message to reflect this, return false and hope for caller response. - case STATE_NEW_KEY_CONTINUE: - mError = ERR_NEW_HOST_KEY; - mErrorMsg = i18n( - "The identity of the remote host '%1' could not be " - "verified. The host's key fingerprint is:\n%2\nYou should " - "verify the fingerprint with the host's administrator before " - "connecting.\n\n" - "Would you like to accept the host's key and connect anyway? " - ).arg(mHost).arg(mKeyFingerprint); - mConnectState = STATE_SEND_CONTINUE; - return false; - - // STATE_DIFF_KEY_WAIT_CONTINUE: - // Grab lines from ssh until we get a continue prompt or a auth - // denied. We will get the later if StrictHostKeyChecking is set - // to yes. Go to STATE_DIFF_KEY_CONTINUE if we get a continue prompt. - case STATE_DIFF_KEY_WAIT_CONTINUE: - line = getLine(); - if( line.isNull() ) { - kdDebug(KSSHPROC) << "KSshProcess::connect(): " - "Got null line in STATE_DIFF_KEY_WAIT_CONTINUE." << endl; - mError = ERR_INTERACT; - mErrorMsg = - i18n("Error encountered while talking to ssh."); - mConnectState = STATE_FATAL; - } - else if( (line.find(authFailedMsg[mVersion]) != -1 - && line.find(tryAgainMsg[mVersion]) == -1) - || line.find(hostKeyVerifyFailedMsg[mVersion]) != -1 ) { - mError = ERR_AUTH_FAILED_DIFF_KEY; - mErrorMsg = i18n( - "WARNING: The identity of the remote host '%1' has changed!\n\n" - "Someone could be eavesdropping on your connection, or the " - "administrator may have just changed the host's key. " - "Either way, you should verify the host's key fingerprint with the host's " - "administrator. The key fingerprint is:\n%2\n" - "Add the correct host key to \"%3\" to " - "get rid of this message." - ).arg(mHost).arg(mKeyFingerprint).arg(mKnownHostsFile); - mConnectState = STATE_FATAL; - } - else if( line.find(continuePrompt[mVersion]) != -1 ) { - mConnectState = STATE_DIFF_KEY_CONTINUE; - } - else if( line.find(keyFingerprintMsg[mVersion]) != -1 ) { - mKeyFingerprint = keyFingerprintMsg[mVersion].cap(); - kdDebug(KSSHPROC) << "Found key fingerprint: " << mKeyFingerprint << endl; - mConnectState = STATE_DIFF_KEY_WAIT_CONTINUE; - } - else if( line.find(knownHostsFileMsg[mVersion]) != -1 ) { - mKnownHostsFile = (knownHostsFileMsg[mVersion]).cap(1); - kdDebug(KSSHPROC) << "Found known hosts file name: " << mKnownHostsFile << endl; - mConnectState = STATE_DIFF_KEY_WAIT_CONTINUE; - } - else { - // ignore line - } - break; - - // STATE_DIFF_KEY_CONTINUE: - // We got a continue prompt for the different key message. - // Set ERR_DIFF_HOST_KEY error - // and return false to signal need to caller action. - case STATE_DIFF_KEY_CONTINUE: - mError = ERR_DIFF_HOST_KEY; - mErrorMsg = i18n( - "WARNING: The identity of the remote host '%1' has changed!\n\n" - "Someone could be eavesdropping on your connection, or the " - "administrator may have just changed the host's key. " - "Either way, you should verify the host's key fingerprint with the host's " - "administrator before connecting. The key fingerprint is:\n%2\n\n" - "Would you like to accept the host's new key and connect anyway?" - ).arg(mHost).arg(mKeyFingerprint); - mConnectState = STATE_SEND_CONTINUE; - return false; - - // STATE_SEND_CONTINUE: - // We found a continue prompt. Send our answer. - case STATE_SEND_CONTINUE: - if( mAcceptHostKey ) { - kdDebug(KSSHPROC) << "KSshProcess::connect(): " - "host key accepted" << endl; - ssh.writeLine("yes"); - mConnectState = STATE_WAIT_PROMPT; - } - else { - kdDebug(KSSHPROC) << "KSshProcess::connect(): " - "host key rejected" << endl; - ssh.writeLine("no"); - mError = ERR_HOST_KEY_REJECTED; - mErrorMsg = i18n("Host key was rejected."); - mConnectState = STATE_FATAL; - } - break; - - // STATE_FATAL: - // Something bad happened that we cannot recover from. - // Kill the ssh process and set flags to show we have - // ended the connection and killed ssh. - // - // mError and mErrorMsg should be set by the immediately - // previous state. - case STATE_FATAL: - kill(); - mConnected = false; - mRunning = false; - mConnectState = STATE_START; - // mError, mErroMsg set by last state - return false; - - default: - kdDebug(KSSHPROC) << "KSshProcess::connect(): " - "Invalid state number - " << mConnectState << endl; - mError = ERR_INVALID_STATE; - mConnectState = STATE_FATAL; - } - } - - // we should never get here - kdDebug(KSSHPROC) << "KSshProcess::connect(): " << - "After switch(). We shouldn't be here." << endl; - mError = ERR_INTERNAL; - return false; -} - -void KSshProcess::disconnect() { - kill(); - mConnected = false; - mRunning = false; - mConnectState = STATE_START; -} - diff --git a/tdeioslave/sftp/ksshprocess.h b/tdeioslave/sftp/ksshprocess.h deleted file mode 100644 index 5130628e4..000000000 --- a/tdeioslave/sftp/ksshprocess.h +++ /dev/null @@ -1,623 +0,0 @@ -/*************************************************************************** - ksshprocess.h - description - ------------------- - begin : Tue Jul 31 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. * - * * - ***************************************************************************/ - -#ifndef KSSHPROCESS_H -#define KSSHPROCESS_H - -#include -#include -#include -#include - -#include - -#include - -#include "process.h" - -#define KSSHPROC 7120 - -/** - * Provides version independent access to ssh. Currently supported - * versions of SSH are: - * OpenSSH 2.9p1 - * OpenSSH 2.9p2 - * OpenSSH 3.0 - * OpenSSH 3.1 - * Commercial SSH 3.0.0 - * Other versions of OpenSSH and commerical SSH will probably work also. - * - * To setup a SSH connection first create a list of options to use and tell - * KSshProcess about your options. Then start the ssh connection. Once the - * connection is setup use the stdin, stdout, stderr, and pty file descriptors - * to communicate with ssh. For a detailed example of how to use, see - * ksshprocesstest.cpp. - * - * @author Lucas Fisher - * - * Example: Connect to ssh server on localhost - * KSshProcess::SshOpt opt; - * KSshProcess::SshOptList options; - * - * opt.opt = KSshProcess::SSH_HOST; - * opt.str = "localhost"; - * options.append(opt); - * - * opt.opt = KSshProcess::SSH_USERNAME; - * opt.str = "me"; - * options.append(opt); - * - * KSshProcess ssh; - * if( !ssh.setOptions(options) ) { - * int err = ssh.error(); - * // process error - * return false; - * } - * - * int err; - * TQString errMsg; - * while( !ssh.connect() ) { - * err = ssh.error(errMsg); - * - * switch( err ) { - * case KSshProcess::ERR_NEW_HOST_KEY: - * case KSshProcess::ERR_DIFF_HOST_KEY: - * // ask user to accept key - * if( acceptHostKey ) { - * ssh.acceptKey(true); - * } - * break; - * - * case KSshProcess::ERR_NEED_PASSWORD: - * // ask user for password - * ssh.password(userPassword); - * break; - * - * case KSshProcess::ERR_NEED_KEY_PASSPHRASE: - * // ask user for their key passphrase - * ssh.keyPassphrase(keyPassphrase); - * break; - * - * default: - * // somethings wrong, alert user - * return; - * } - * } - * // We have an open ssh connection to localhost - * - */ - -class KSshProcess { -public: - /** - * SSH Option - * - * Stores SSH options for use with KSshProcess. - * - * SSH options are configured much like UDS entries. - * Each option is assigned a constant and a string, bool, - * or number is assigned based on the option. - * - * @author Lucas Fisher (ljfisher@iastate.edu) - */ - class SshOpt { - public: - TQ_UINT32 opt; - TQString str; - TQ_INT32 num; - bool boolean; - }; - - /** - * List of SshOptions and associated iterators - */ - typedef TQValueList SshOptList; - typedef TQValueListIterator SshOptListIterator; - typedef TQValueListConstIterator SshOptListConstIterator; - - /** - * Ssh versions supported by KSshProcess. Subject to change - * at any time. - */ - enum SshVersion { - OPENSSH_3_6, - OPENSSH, - SSH, - SSH_VER_MAX, - UNKNOWN_VER - }; - - /** - * SSH options supported by KSshProcess. Set SshOpt::opt to one of these - * values. - */ - // we cannot do this like UDSAtomType (ORing the type with the name) because - // we have too many options for ssh and not enough bits. - enum SshOptType { - /** - * Request server to invoke subsystem. (str) - */ - SSH_SUBSYSTEM, - /** - * Connect to port on the server. (num) - */ - SSH_PORT, - /** - * Connect to host. (str) - */ - SSH_HOST, - /** - * connect using this username. (str) - */ - SSH_USERNAME, - /** - * connect using this password. (str) - */ - SSH_PASSWD, - /** - * connect using this version of the SSH protocol. num == 1 or 2 - */ - SSH_PROTOCOL, - /** - * whether to forward X11 connections. (boolean) - */ - SSH_FORWARDX11, - /** - * whether to do agent forwarding. (boolean) - */ - SSH_FORWARDAGENT, - /** - * use as escape character. 0 for none (num) - */ - SSH_ESCAPE_CHAR, - /** - * command for ssh to perform once it is connected (str) - */ - SSH_COMMAND, - /** - * Set ssh verbosity. This may be added multiple times. It may also cause KSSHProcess - * to fail since we don't understand all the debug messages. - */ - SSH_VERBOSE, - /** - * Set a ssh option as one would find in the ssh_config file - * The str member should be set to 'optName value' - */ - SSH_OPTION, - /** - * Set some other option not supported by KSSHProcess. The option should - * be specified in the str member of SshOpt. Careful with this since - * not all versions of SSH support the same options. - */ - SSH_OTHER, - SSH_OPT_MAX // always last - }; // that's all for now - - /** - * Errors that KSshProcess can encounter. When a member function returns - * false, call error() to retrieve one of these error codes. - */ - enum SshError { - /** - * Don't recognize the ssh version - */ - ERR_UNKNOWN_VERSION, - /** - * Cannot lauch ssh client - */ - ERR_CANNOT_LAUNCH, - /** - * Interaction with the ssh client failed. This happens when we can't - * find the password prompt or something similar - */ - ERR_INTERACT, - /** - * Arguments for both a remotely executed subsystem and command were provide. - * Only one or the other may be used - */ - ERR_CMD_SUBSYS_CONFLICT, - /** - * No password was supplied - */ - ERR_NEED_PASSWD, - /** - * No passphrase was supplied. - */ - ERR_NEED_PASSPHRASE, - /** - * No usename was supplied - */ - ERR_NEED_USERNAME, - /** - * Timed out waiting for a response from ssh or the server - */ - ERR_TIMED_OUT, - /** - * Internal error, probably from a system call - */ - ERR_INTERNAL, - /** - * ssh was disconnect from the host - */ - ERR_DISCONNECTED, - /** - * No ssh options have been set. Call setArgs() before calling connect. - */ - ERR_NO_OPTIONS, - /** - * A host key was received from an unknown host. - * Call connect() with the acceptHostKey argument to accept the key. - */ - ERR_NEW_HOST_KEY, - /** - * A host key different from what is stored in the user's known_hosts file - * has be received. This is an indication of an attack - */ - ERR_DIFF_HOST_KEY, - /** - * A new or different host key was rejected by the caller. The ssh - * connection was terminated and the ssh process killed. - */ - ERR_HOST_KEY_REJECTED, - /** - * An invalid option was found in the SSH option list - */ - ERR_INVALID_OPT, - /** - * SSH accepted host key without prompting user. - */ - ERR_ACCEPTED_KEY, - /** - * Authentication failed - */ - ERR_AUTH_FAILED, - /** - * Authentication failed because a new host key was detected and - * SSH is configured with strict host key checking enabled. - */ - ERR_AUTH_FAILED_NEW_KEY, - /** - * Authentication failed because a changed host key was detected and - * SSH is configured with strict host key checking enabled. - */ - ERR_AUTH_FAILED_DIFF_KEY, - /** - * The remote host closed the connection for unknown reasons. - */ - ERR_CLOSED_BY_REMOTE_HOST, - /** - * We have no idea what happened - */ - ERR_UNKNOWN, - /** - * The connect state machine entered an invalid state. - */ - ERR_INVALID_STATE, - ERR_MAX - }; - - /** - * Initialize a SSH process using the first SSH binary found in the PATH - */ - KSshProcess(); - - /** - * Initialize a SSH process using the specified SSH binary. - * @param pathToSsh The fully qualified path name of the ssh binary - * KSshProcess should use to setup a SSH connection. - */ - KSshProcess(TQString pathToSsh); - ~KSshProcess(); - - /** - * Set the ssh binary KSshProcess should use. This will only affect the - * next ssh connection attempt using this instance. - * - * @param pathToSsh Full path to the ssh binary. - * - * @return True if the ssh binary is found and KSshProcess - * recognizes the version. - * - */ - bool setSshPath(TQString pathToSsh); - - /** - * Get the ssh version. - * - * @return The ssh version or -1 if KSshProcess does not recognize - * the ssh version. The returned value corresponds to the - * member of the SshVersion enum. - */ - SshVersion version(); - - /** - * Get a string describing the ssh version - * - * @return A string describing the ssh version recognized by KSshProcess - */ - //TQString versionStr(); - - /** - * Get the last error encountered by KSshProcess. - * - * @param msg Set to the error message, if any, outputted by ssh when it is run. - * - * @return The error number. See SshError for descriptions. - */ - int error(TQString& msg); - - /** - * Get the last error encountered by KSshProcess. - * @return The error number. See SshError for descriptions. - */ - int error() { return mError; } - - TQString errorMsg() { return mErrorMsg; } - - /** - * Send a signal to the ssh process. Do not use this to end the - * ssh connection as it will not correctly reset the internal - * state of the KSshProcess object. Use KSshProcess::disconnect() - * instead. - * - * @param signal The signal to send to the ssh process. See 'kill -l' - * for a list of possible signals. - * The default signal is SIGKILL which kills ssh. - * - */ - void kill(int signal = SIGKILL); - - /** - * The pid of the ssh process started by this instance of KSshProcess. - * Only valid if KSshProcess::running() returns true; - * - * @return The pid of the running ssh process. - */ - int pid() { return ssh.pid(); } - - /** - * Whether a ssh connection has been established with a - * remote host. A establish connection means ssh has successfully - * authenticated with the remote host and user data can be transfered - * between the local and remote host. This cannot return - * true unless the most recent call to KSshProccess::connect() returned true. - * - * @return True if a ssh connection has been established with a remote - * host. False otherwise. - */ - bool connected() { return mConnected; } - - /** - * Whether a ssh process is currently running. This only indicates - * if a ssh process has been started and is still running. It does not - * tell if authentication has been successful. This may return true - * even if the most recent call to KSshProcess::connect() returned false. - * - * @return True if a ssh process started by this instance of KSshProcess - * is running. False otherwise. - */ - bool running() { return mRunning; } - - /** - * Print the command line arguments ssh is run with using kdDebug. - */ - void printArgs(); - - /** - * Set the SSH options. - * This must be called before connect(). See SshOptType for a list of - * supported ssh options. The required options are SSH_USERNAME - * and SSH_HOST. - * - * To reset the saved options, just recall setOptions() again with - * a different options list. - * - * @param opts A list of SshOpt objects specifying the ssh options. - * - * @return True if all options are valid. False if unrecognized options - * or a required option is missing. Call error() - * for details. - * - */ - bool setOptions(const SshOptList& opts); - - /** - * Create a ssh connection based on the options provided by setOptions(). - * Sets one of the following error codes on failure: - *
    - *
  • ERR_NO_OPTIONS
  • - *
  • ERR_CANNOT_LAUNCH
  • - *
  • ERR_INVALID_STATE
  • - *
  • ERR_NEED_PASSWD
  • - *
  • ERR_AUTH_FAILED
  • - *
  • ERR_NEW_HOST_KEY
  • - *
  • ERR_KEY_ACCEPTED
  • - *
  • ERR_DIFF_HOST_KEY
  • - *
  • ERR_INTERNAL
  • - *
  • ERR_INTERACT
  • - *
- * - * @param acceptHostKey When true KSshProcess will automatically accept - * unrecognized or changed host keys. - * - * @return True if the ssh connection is successful. False if the connection - * fails. Call error() to get the reason for the failure. - */ - bool connect(); - - - /** - * Disconnect ssh from the host. This kills the ssh process and - * resets the internal state of this KSshProcess object. After a - * disconnect, the same KSshProcess can be used to connect to a - * host. - */ - void disconnect(); - - /** - * Call to respond to a ERR_NEW_HOST_KEY or ERR_DIFF_HOST_KEY error. - * - * @param accept True to accept the host key, false to not accept the - * host key and kill ssh. - * - */ - void acceptHostKey(bool accept); - - /** - * Call to respond to a ERR_NEED_PASSWD or ERR_NEED_PASSPHRASE error. - * - * @param password The user password to give ssh. - */ - void setPassword(TQString password); - - /** - * Access to standard in and out of the ssh process. - * - * @return The file description for stdin and stdout of the ssh process. - */ - int stdioFd() { return ssh.stdioFd(); } - - /** - * Access to standard error of the ssh process. - * - * @return The file descriptior for stderr of the ssh process. - */ - int stderrFd() { return ssh.stderrFd(); } - - /** - * Access the pty to which the ssh process is attached. - * - * @return The file descriptor of pty to which ssh is attached. - */ - int pty() { return ssh.fd(); } -private: - /** - * Path the the ssh binary. - */ - TQString mSshPath; - - /** - * SSH version. This is an index into the supported SSH - * versions array, and the various messages arrays. - */ - SshVersion mVersion; - - /** - * User's password. Zero this out when it is no longer needed. - */ - TQString mPassword; - - /** - * User's username. - */ - TQString mUsername; - - /** - * Name of host we are connecting to. - */ - TQString mHost; - - /** - * Accept new or changed host keys if true. - */ - bool mAcceptHostKey; - - /** - * Flag to tell use if we have an open, authenticated ssh - * session going. - */ - bool mConnected; - - /** - * Flag to tell us if we have started a ssh process, we use this - * to make sure we kill ssh before going away. - */ - bool mRunning; - - /** - * Save any key fingerprint msg from ssh so we can present - * it to the caller. - */ - TQString mKeyFingerprint; - - /** - * The location of the known host key file. We grab this from - * any error messages ssh prints out. - */ - TQString mKnownHostsFile; - - /** - * The state of our connect state machine. - */ - int mConnectState; - - /** - * Port on on which the target ssh server is listening. - */ - int mPort; - - /** - * The last error number encountered. This is only valid for the - * last error. - */ - SshError mError; - - /** - * An error message that corresponds to the error number set in - * mError. Optional. - */ - TQString mErrorMsg; - - /** - * Interface to the SSH process we ceate. Handles communication - * to and from the SSH process using stdin, stdout, stderr, and - * pty. - */ - MyPtyProcess ssh; - - /** - * List of arguments we start SSH with. - */ - QCStringList mArgs; - void init(); - - /** - * Handler to clean up when ssh process terminates. - */ - static void SIGCHLD_handler(int signo); - void installSignalHandlers(); - void removeSignalHandlers(); - - TQString getLine(); - - static TQRegExp versionStrs[]; - static const char * const passwordPrompt[]; - static const char * const passphrasePrompt[]; - static const char * const authSuccessMsg[]; - static const char * const authFailedMsg[]; - static TQRegExp hostKeyMissingMsg[]; - static const char * const hostKeyChangedMsg[]; - static const char * const continuePrompt[]; - static const char * const hostKeyAcceptedMsg[]; - static const char * const tryAgainMsg[]; - static TQRegExp hostKeyVerifyFailedMsg[]; - static const char * const connectionClosedMsg[]; - static const char * const changeHostKeyOnDiskPrompt[]; - static TQRegExp keyFingerprintMsg[]; - static TQRegExp knownHostsFileMsg[]; -}; -#endif diff --git a/tdeioslave/sftp/ksshprocesstest.cpp b/tdeioslave/sftp/ksshprocesstest.cpp deleted file mode 100644 index 59dbf58c7..000000000 --- a/tdeioslave/sftp/ksshprocesstest.cpp +++ /dev/null @@ -1,98 +0,0 @@ -#include "ksshprocess.h" -#include - -using namespace std; - -int main(int argc, char *argv[]) { - - if( argc < 5 ) { - cout << "Usage: " << argv[0] << - " " << endl; - return 1; - } - - KSshProcess ssh(argv[1]); - cout << ssh.version() << endl; - - KSshProcess::SshOptList opts; - KSshProcess::SshOpt opt; - - opt.opt = KSshProcess::SSH_PORT; - opt.num = 22; - opts.append(opt); - - opt.opt = KSshProcess::SSH_HOST; - opt.str = TQString(argv[2]); - opts.append(opt); - - opt.opt = KSshProcess::SSH_USERNAME; - opt.str = TQString(argv[3]); - opts.append(opt); - -// opt.opt = KSshProcess::SSH_PASSWD; -// opt.str = TQString(argv[4]); -// opts.append(opt); - - if( !ssh.setOptions(opts) ) { - cout << "ksshprocesstest: setOptions failed" << endl; - return -1; - } - - ssh.printArgs(); - - bool stop = false; - bool connected; - char buf[256]; - char c; - while( !stop && !(connected = ssh.connect()) ) { - cout << "ksshprocesstest: Error num - " << ssh.error() << endl; - cout << "ksshprocesstest: Error msg - " << ssh.errorMsg().latin1() << endl; - switch( ssh.error() ) { - case KSshProcess::ERR_NEED_PASSWD: - case KSshProcess::ERR_NEED_PASSPHRASE: - cout << "Password: "; - cin >> buf; - cout << "password is " << buf << endl; - ssh.setPassword(TQString(buf)); - break; - case KSshProcess::ERR_NEW_HOST_KEY: - case KSshProcess::ERR_DIFF_HOST_KEY: - cout << "Accept host key? (y/n): "; - cin >> c; - cout << "Answered " << c << endl; - ssh.acceptHostKey(c == 'y' ? true : false); - break; - case KSshProcess::ERR_AUTH_FAILED: - cout << "ksshprocesstest: auth failed." << endl; - stop = true; - break; - case KSshProcess::ERR_AUTH_FAILED_NEW_KEY: - cout << "ksshprocesstest: auth failed because of new key." << endl; - stop = true; - break; - case KSshProcess::ERR_AUTH_FAILED_DIFF_KEY: - cout << "ksshprocesstest: auth failed because of changed key." << endl; - stop = true; - break; - - 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: - cout << "ksshprocesstest: FATAL ERROR" << endl; - stop = true; - break; - - } - } - - if( connected ) { - cout << "ksshprocesstest: Successfully connected to " << argv[2] << endl; - } - else { - cout << "ksshprocesstest: Connect to " << argv[2] << " failed." << endl; - } - -} diff --git a/tdeioslave/sftp/process.cpp b/tdeioslave/sftp/process.cpp deleted file mode 100644 index 164121497..000000000 --- a/tdeioslave/sftp/process.cpp +++ /dev/null @@ -1,493 +0,0 @@ -/* - * - * - * This file is part of the KDE project, module tdesu. - * Copyright (C) 1999,2000 Geert Jansen - * - * This file contains code from TEShell.C of the KDE konsole. - * Copyright (c) 1997,1998 by Lars Doelle - * - * This is free software; you can use this library under the GNU Library - * General Public License, version 2. See the file "COPYING.LIB" for the - * exact licensing terms. - * - * process.cpp: Functionality to build a front end to password asking - * terminal programs. - */ - -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include -#include -#include -#include -#include - -#if defined(__SVR4) && defined(sun) -#include -#include -#endif - -#ifdef HAVE_SYS_SELECT_H -#include // Needed on some systems. -#endif - -#include -#include -#include - -#include -#include - -#include "process.h" -#include -#include - - -MyPtyProcess::MyPtyProcess() -{ - m_bTerminal = false; - m_bErase = false; - m_pPTY = 0L; - m_Pid = -1; - m_Fd = -1; -} - - -int MyPtyProcess::init() -{ - delete m_pPTY; - m_pPTY = new PTY(); - m_Fd = m_pPTY->getpt(); - if (m_Fd < 0) - return -1; - if ((m_pPTY->grantpt() < 0) || (m_pPTY->unlockpt() < 0)) - { - kdError(PTYPROC) << k_lineinfo << "Master setup failed.\n" << endl; - m_Fd = -1; - return -1; - } - m_TTY = m_pPTY->ptsname(); - m_stdoutBuf.resize(0); - m_stderrBuf.resize(0); - m_ptyBuf.resize(0); - return 0; -} - - -MyPtyProcess::~MyPtyProcess() -{ - delete m_pPTY; -} - - -/* - * Read one line of input. The terminal is in canonical mode, so you always - * read a line at at time, but it's possible to receive multiple lines in - * one time. - */ - - -TQCString MyPtyProcess::readLineFrom(int fd, TQCString& inbuf, bool block) -{ - int pos; - TQCString ret; - - if (!inbuf.isEmpty()) - { - - pos = inbuf.find('\n'); - - if (pos == -1) - { - ret = inbuf; - inbuf.resize(0); - } else - { - ret = inbuf.left(pos); - inbuf = inbuf.mid(pos+1); - } - return ret; - - } - - int flags = fcntl(fd, F_GETFL); - if (flags < 0) - { - kdError(PTYPROC) << k_lineinfo << "fcntl(F_GETFL): " << perror << "\n"; - return ret; - } - if (block) - flags &= ~O_NONBLOCK; - else - flags |= O_NONBLOCK; - if (fcntl(fd, F_SETFL, flags) < 0) - { - kdError(PTYPROC) << k_lineinfo << "fcntl(F_SETFL): " << perror << "\n"; - return ret; - } - - int nbytes; - char buf[256]; - while (1) - { - nbytes = read(fd, buf, 255); - if (nbytes == -1) - { - if (errno == EINTR) - continue; - else break; - } - if (nbytes == 0) - break; // eof - - buf[nbytes] = '\000'; - inbuf += buf; - - pos = inbuf.find('\n'); - if (pos == -1) - { - ret = inbuf; - inbuf.resize(0); - } else - { - ret = inbuf.left(pos); - inbuf = inbuf.mid(pos+1); - } - break; - - } - - return ret; -} - -void MyPtyProcess::writeLine(TQCString line, bool addnl) -{ - if (!line.isEmpty()) - write(m_Fd, line, line.length()); - if (addnl) - write(m_Fd, "\n", 1); -} - -void MyPtyProcess::unreadLineFrom(TQCString inbuf, TQCString line, bool addnl) -{ - if (addnl) - line += '\n'; - if (!line.isEmpty()) - inbuf.prepend(line); -} - - -/* - * Fork and execute the command. This returns in the parent. - */ - -int MyPtyProcess::exec(TQCString command, QCStringList args) -{ - kdDebug(PTYPROC) << "MyPtyProcess::exec(): " << command << endl;// << ", args = " << args << endl; - - if (init() < 0) - return -1; - - // Open the pty slave before forking. See SetupTTY() - int slave = open(m_TTY, O_RDWR); - if (slave < 0) - { - kdError(PTYPROC) << k_lineinfo << "Could not open slave pty.\n"; - return -1; - } - - // Also create a socket pair to connect to standard in/out. - // This will allow use to bypass the terminal. - int inout[2]; - int err[2]; - int ok = 1; - ok &= socketpair(AF_UNIX, SOCK_STREAM, 0, inout) >= 0; - ok &= socketpair(AF_UNIX, SOCK_STREAM, 0, err ) >= 0; - if( !ok ) { - kdDebug(PTYPROC) << "Could not create socket" << endl; - return -1; - } - m_stdinout = inout[0]; - m_err = err[0]; - - if ((m_Pid = fork()) == -1) - { - kdError(PTYPROC) << k_lineinfo << "fork(): " << perror << "\n"; - return -1; - } - - // Parent - if (m_Pid) - { - close(slave); - close(inout[1]); - close(err[1]); - return 0; - } - - // Child - - ok = 1; - ok &= dup2(inout[1], STDIN_FILENO) >= 0; - ok &= dup2(inout[1], STDOUT_FILENO) >= 0; - ok &= dup2(err[1], STDERR_FILENO) >= 0; - - if( !ok ) - { - kdError(PTYPROC) << "dup of socket descriptor failed" << endl; - _exit(1); - } - - close(inout[1]); - close(inout[0]); - close(err[1]); - close(err[0]); - - if (SetupTTY(slave) < 0) - _exit(1); - - // From now on, terminal output goes through the tty. - TQCString path; - if (command.contains('/')) - path = command; - else - { - TQString file = TDEStandardDirs::findExe(command); - if (file.isEmpty()) - { - kdError(PTYPROC) << k_lineinfo << command << " not found\n"; - _exit(1); - } - path = TQFile::encodeName(file); - } - - int i; - const char * argp[32]; - argp[0] = path; - QCStringList::Iterator it; - for (i=1, it=args.begin(); it!=args.end() && i<31; it++) { - argp[i++] = *it; - kdDebug(PTYPROC) << *it << endl; - } - argp[i] = 0L; - execv(path, (char * const *)argp); - kdError(PTYPROC) << k_lineinfo << "execv(\"" << path << "\"): " << perror << "\n"; - _exit(1); - return -1; // Shut up compiler. Never reached. -} - -/* - * Wait until the terminal is set into no echo mode. At least one su - * (RH6 w/ Linux-PAM patches) sets noecho mode AFTER writing the Password: - * prompt, using TCSAFLUSH. This flushes the terminal I/O queues, possibly - * taking the password with it. So we wait until no echo mode is set - * before writing the password. - * Note that this is done on the slave fd. While Linux allows tcgetattr() on - * the master side, Solaris doesn't. - */ - -int MyPtyProcess::WaitSlave() -{ - int slave = open(m_TTY, O_RDWR); - if (slave < 0) - { - kdError(PTYPROC) << k_lineinfo << "Could not open slave tty.\n"; - return -1; - } - - struct termios tio; - struct timeval tv; - while (1) - { - if (tcgetattr(slave, &tio) < 0) - { - kdError(PTYPROC) << k_lineinfo << "tcgetattr(): " << perror << "\n"; - close(slave); - return -1; - } - if (tio.c_lflag & ECHO) - { - kdDebug(PTYPROC) << k_lineinfo << "Echo mode still on." << endl; - // sleep 1/10 sec - tv.tv_sec = 0; tv.tv_usec = 100000; - select(slave, 0L, 0L, 0L, &tv); - continue; - } - break; - } - close(slave); - return 0; -} - - -int MyPtyProcess::enableLocalEcho(bool enable) -{ - int slave = open(m_TTY, O_RDWR); - if (slave < 0) - { - kdError(PTYPROC) << k_lineinfo << "Could not open slave tty.\n"; - return -1; - } - struct termios tio; - if (tcgetattr(slave, &tio) < 0) - { - kdError(PTYPROC) << k_lineinfo << "tcgetattr(): " << perror << "\n"; - close(slave); return -1; - } - if (enable) - tio.c_lflag |= ECHO; - else - tio.c_lflag &= ~ECHO; - if (tcsetattr(slave, TCSANOW, &tio) < 0) - { - kdError(PTYPROC) << k_lineinfo << "tcsetattr(): " << perror << "\n"; - close(slave); return -1; - } - close(slave); - return 0; -} - - -/* - * Copy output to stdout until the child process exists, or a line of output - * matches `m_Exit'. - * We have to use waitpid() to test for exit. Merely waiting for EOF on the - * pty does not work, because the target process may have children still - * attached to the terminal. - */ - -int MyPtyProcess::waitForChild() -{ - int ret, state, retval = 1; - struct timeval tv; - - fd_set fds; - FD_ZERO(&fds); - - while (1) - { - tv.tv_sec = 1; tv.tv_usec = 0; - FD_SET(m_Fd, &fds); - ret = select(m_Fd+1, &fds, 0L, 0L, &tv); - if (ret == -1) - { - if (errno == EINTR) continue; - else - { - kdError(PTYPROC) << k_lineinfo << "select(): " << perror << "\n"; - return -1; - } - } - - if (ret) - { - TQCString line = readLine(false); - while (!line.isNull()) - { - if (!m_Exit.isEmpty() && !tqstrnicmp(line, m_Exit, m_Exit.length())) - kill(m_Pid, SIGTERM); - if (m_bTerminal) - { - fputs(line, stdout); - fputc('\n', stdout); - } - line = readLine(false); - } - } - - // Check if the process is still alive - ret = waitpid(m_Pid, &state, WNOHANG); - if (ret < 0) - { - if (errno == ECHILD) - retval = 0; - else - kdError(PTYPROC) << k_lineinfo << "waitpid(): " << perror << "\n"; - break; - } - if (ret == m_Pid) - { - if (WIFEXITED(state)) - retval = WEXITSTATUS(state); - break; - } - } - - return -retval; -} - -/* - * SetupTTY: Creates a new session. The filedescriptor "fd" should be - * connected to the tty. It is closed after the tty is reopened to make it - * our controlling terminal. This way the tty is always opened at least once - * so we'll never get EIO when reading from it. - */ - -int MyPtyProcess::SetupTTY(int fd) -{ - // Reset signal handlers - for (int sig = 1; sig < NSIG; sig++) - signal(sig, SIG_DFL); - signal(SIGHUP, SIG_IGN); - - // Close all file handles -// struct rlimit rlp; -// getrlimit(RLIMIT_NOFILE, &rlp); -// for (int i = 0; i < (int)rlp.rlim_cur; i++) -// if (i != fd) close(i); - - // Create a new session. - setsid(); - - // Open slave. This will make it our controlling terminal - int slave = open(m_TTY, O_RDWR); - if (slave < 0) - { - kdError(PTYPROC) << k_lineinfo << "Could not open slave side: " << perror << "\n"; - return -1; - } - close(fd); - -#if defined(__SVR4) && defined(sun) - - // Solaris STREAMS environment. - // Push these modules to make the stream look like a terminal. - ioctl(slave, I_PUSH, "ptem"); - ioctl(slave, I_PUSH, "ldterm"); - -#endif - - // Connect stdin, stdout and stderr -// dup2(slave, 0); dup2(slave, 1); dup2(slave, 2); -// if (slave > 2) -// close(slave); - - // Disable OPOST processing. Otherwise, '\n' are (on Linux at least) - // translated to '\r\n'. - struct termios tio; - if (tcgetattr(slave, &tio) < 0) - { - kdError(PTYPROC) << k_lineinfo << "tcgetattr(): " << perror << "\n"; - return -1; - } - tio.c_oflag &= ~OPOST; - if (tcsetattr(slave, TCSANOW, &tio) < 0) - { - kdError(PTYPROC) << k_lineinfo << "tcsetattr(): " << perror << "\n"; - return -1; - } - - return 0; -} diff --git a/tdeioslave/sftp/process.h b/tdeioslave/sftp/process.h deleted file mode 100644 index ca154f45c..000000000 --- a/tdeioslave/sftp/process.h +++ /dev/null @@ -1,148 +0,0 @@ -/* - * - * - * This file is part of the KDE project, module tdesu. - * Copyright (C) 1999,2000 Geert Jansen - * - * This is free software; you can use this library under the GNU Library - * General Public License, version 2. See the file "COPYING.LIB" for the - * exact licensing terms. - */ - -#ifndef __Process_h_Included__ -#define __Process_h_Included__ - -#include -#include -#include -#include - -#define PTYPROC 7120 - -class PTY; -typedef TQValueList QCStringList; - -/** - * Synchronous communication with tty programs. - * - * PtyProcess provides synchronous communication with tty based programs. - * The communications channel used is a pseudo tty (as opposed to a pipe) - * This means that programs which require a terminal will work. - */ - -class MyPtyProcess -{ -public: - MyPtyProcess(); - virtual ~MyPtyProcess(); - - /** - * Fork off and execute a command. The command's standard in and output - * are connected to the pseudo tty. They are accessible with @ref #readLine - * and @ref #writeLine. - * @param command The command to execute. - * @param args The arguments to the command. - */ - int exec(TQCString command, QCStringList args); - - /** - * Read a line from the program's standard out. Depending on the @em block - * parameter, this call blocks until a single, full line is read. - * @param block Block until a full line is read? - * @return The output string. - */ - TQCString readLine(bool block = true) - { return readLineFrom(m_Fd, m_ptyBuf, block); } - - TQCString readLineFromPty(bool block = true) - { return readLineFrom(m_Fd, m_ptyBuf, block); } - - TQCString readLineFromStdout(bool block = true) - { return readLineFrom(m_stdinout, m_stdoutBuf, block); } - - TQCString readLineFromStderr(bool block = true) - { return readLineFrom(m_err, m_stderrBuf, block); } - - /** - * Write a line of text to the program's standard in. - * @param line The text to write. - * @param addNewline Adds a '\n' to the line. - */ - void writeLine(TQCString line, bool addNewline=true); - - /** - * Put back a line of input. - * @param line The line to put back. - * @param addNewline Adds a '\n' to the line. - */ - - void unreadLine(TQCString line, bool addNewline = true) - { unreadLineFrom(m_ptyBuf, line, addNewline); } - - void unreadLineFromPty(TQCString line, bool addNewline = true) - { unreadLineFrom(m_ptyBuf, line, addNewline); } - - void unreadLineFromStderr(TQCString line, bool addNewline = true) - { unreadLineFrom(m_stderrBuf, line, addNewline); } - - void unreadLineFromStdout(TQCString line, bool addNewline = true) - { unreadLineFrom(m_stdoutBuf, line, addNewline); } - - /** - * Set exit string. If a line of program output matches this, - * @ref #waitForChild() will terminate the program and return. - */ - void setExitString(TQCString exit) { m_Exit = exit; } - - /** - * Wait for the child to exit. See also @ref #setExitString. - */ - int waitForChild(); - - /** - * Wait until the pty has cleared the ECHO flag. This is useful - * when programs write a password prompt before they disable ECHO. - * Disabling it might flush any input that was written. - */ - int WaitSlave(); - - /** Enables/disables local echo on the pseudo tty. */ - int enableLocalEcho(bool enable=true); - - /** Enable/disable terminal output. Relevant only to some subclasses. */ - void setTerminal(bool terminal) { m_bTerminal = terminal; } - - /** Overwritte the password as soon as it is used. Relevant only to - * some subclasses. */ - void setErase(bool erase) { m_bErase = erase; } - - /** Return the filedescriptor of the process. */ - int fd() {return m_Fd;} - - /** Return the pid of the process. */ - int pid() {return m_Pid;} - - int stdioFd() {return m_stdinout;} - - int stderrFd() {return m_err;} - -protected: - bool m_bErase, m_bTerminal; - int m_Pid, m_Fd, m_stdinout, m_err; - TQCString m_Command, m_Exit; - -private: - int init(); - int SetupTTY(int fd); - - PTY *m_pPTY; - TQCString m_TTY; - TQCString m_ptyBuf, m_stderrBuf, m_stdoutBuf; - - TQCString readLineFrom(int fd, TQCString& inbuf, bool block); - void unreadLineFrom(TQCString inbuf, TQCString line, bool addnl); - class PtyProcessPrivate; - PtyProcessPrivate *d; -}; - -#endif diff --git a/tdeioslave/sftp/sftp.h b/tdeioslave/sftp/sftp.h deleted file mode 100644 index 95518130d..000000000 --- a/tdeioslave/sftp/sftp.h +++ /dev/null @@ -1,91 +0,0 @@ -/* $OpenBSD: sftp.h,v 1.3 2001/03/07 10:11:23 djm Exp $ */ - -/* - * Copyright (c) 2001 Markus Friedl. All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions - * are met: - * 1. Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * 2. Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR - * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES - * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. - * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, - * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT - * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, - * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY - * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF - * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -/* - * draft-ietf-secsh-filexfer-01.txt - */ - -/* version */ -#define SSH2_FILEXFER_VERSION 3 - -/* client to server */ -#define SSH2_FXP_INIT 1 -#define SSH2_FXP_OPEN 3 -#define SSH2_FXP_CLOSE 4 -#define SSH2_FXP_READ 5 -#define SSH2_FXP_WRITE 6 -#define SSH2_FXP_LSTAT 7 -#define SSH2_FXP_FSTAT 8 -#define SSH2_FXP_SETSTAT 9 -#define SSH2_FXP_FSETSTAT 10 -#define SSH2_FXP_OPENDIR 11 -#define SSH2_FXP_READDIR 12 -#define SSH2_FXP_REMOVE 13 -#define SSH2_FXP_MKDIR 14 -#define SSH2_FXP_RMDIR 15 -#define SSH2_FXP_REALPATH 16 -#define SSH2_FXP_STAT 17 -#define SSH2_FXP_RENAME 18 -#define SSH2_FXP_READLINK 19 -#define SSH2_FXP_SYMLINK 20 - -/* server to client */ -#define SSH2_FXP_VERSION 2 -#define SSH2_FXP_STATUS 101 -#define SSH2_FXP_HANDLE 102 -#define SSH2_FXP_DATA 103 -#define SSH2_FXP_NAME 104 -#define SSH2_FXP_ATTRS 105 - -#define SSH2_FXP_EXTENDED 200 -#define SSH2_FXP_EXTENDED_REPLY 201 - -/* attributes */ -#define SSH2_FILEXFER_ATTR_SIZE 0x00000001 -#define SSH2_FILEXFER_ATTR_UIDGID 0x00000002 -#define SSH2_FILEXFER_ATTR_PERMISSIONS 0x00000004 -#define SSH2_FILEXFER_ATTR_ACMODTIME 0x00000008 -#define SSH2_FILEXFER_ATTR_EXTENDED 0x80000000 - -/* portable open modes */ -#define SSH2_FXF_READ 0x00000001 -#define SSH2_FXF_WRITE 0x00000002 -#define SSH2_FXF_APPEND 0x00000004 -#define SSH2_FXF_CREAT 0x00000008 -#define SSH2_FXF_TRUNC 0x00000010 -#define SSH2_FXF_EXCL 0x00000020 - -/* status messages */ -#define SSH2_FX_OK 0 -#define SSH2_FX_EOF 1 -#define SSH2_FX_NO_SUCH_FILE 2 -#define SSH2_FX_PERMISSION_DENIED 3 -#define SSH2_FX_FAILURE 4 -#define SSH2_FX_BAD_MESSAGE 5 -#define SSH2_FX_NO_CONNECTION 6 -#define SSH2_FX_CONNECTION_LOST 7 -#define SSH2_FX_OP_UNSUPPORTED 8 -#define SSH2_FX_MAX 8 diff --git a/tdeioslave/sftp/sftp.protocol b/tdeioslave/sftp/sftp.protocol index 33e66867a..a317beb25 100644 --- a/tdeioslave/sftp/sftp.protocol +++ b/tdeioslave/sftp/sftp.protocol @@ -2,7 +2,7 @@ exec=tdeio_sftp protocol=sftp input=none -listing=Name,Type,Size,Date,Access,Owner,Group,Link +listing=Name,Type,Size,Date,Access,Owner,Group,Link,MimeType output=filesystem copyToFile=true copyFromFile=true @@ -12,7 +12,7 @@ makedir=true deleting=true moving=true Icon=ftp -Description=A tdeioslave for sftp +Description=A new tdeioslave for sftp X-DocPath=tdeioslave/sftp/index.html Icon=ftp Class=:internet diff --git a/tdeioslave/sftp/sftpfileattr.cpp b/tdeioslave/sftp/sftpfileattr.cpp deleted file mode 100644 index ddd94bbe8..000000000 --- a/tdeioslave/sftp/sftpfileattr.cpp +++ /dev/null @@ -1,345 +0,0 @@ -/*************************************************************************** - sftpfileattr.cpp - description - ------------------- - begin : Sat Jun 30 2001 - copyright : (C) 2001 by Lucas Fisher - email : ljfisher@iastate.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. * - * * - ***************************************************************************/ - -#include "sftpfileattr.h" - -#include -#include -#include - -#include -#include - -#include -#include - -using namespace TDEIO; - -sftpFileAttr::sftpFileAttr(){ - clear(); - mDirAttrs = false; -} - -sftpFileAttr::sftpFileAttr(KRemoteEncoding* encoding){ - clear(); - mDirAttrs = false; - mEncoding = encoding; -} - - -/** Constructor to initialize the file attributes on declaration. */ -sftpFileAttr::sftpFileAttr(TQ_ULLONG size, uid_t uid, gid_t gid, - mode_t permissions, time_t atime, - time_t mtime, TQ_UINT32 extendedCount) { - clear(); - mDirAttrs = false; - mSize = size; - mUid = uid; - mGid = gid; - mAtime = atime; - mMtime = mtime; - mPermissions = permissions; - mExtendedCount = extendedCount; -} - -sftpFileAttr::~sftpFileAttr(){ -} - -/** Returns a UDSEntry describing the file. -The UDSEntry is generated from the sftp file attributes. */ -UDSEntry sftpFileAttr::entry() { - UDSEntry entry; - UDSAtom atom; - - atom.m_uds = UDS_NAME; - atom.m_str = mFilename; - entry.append(atom); - - if( mFlags & SSH2_FILEXFER_ATTR_SIZE ) { - atom.m_uds = UDS_SIZE; - atom.m_long = mSize; - entry.append(atom); - } - - if( mFlags & SSH2_FILEXFER_ATTR_ACMODTIME ) { - atom.m_uds = UDS_ACCESS_TIME; - atom.m_long = mAtime; - entry.append(atom); - - atom.m_uds = UDS_MODIFICATION_TIME; - atom.m_long = mMtime; - entry.append(atom); - } - - if( mFlags & SSH2_FILEXFER_ATTR_UIDGID ) { - if( mUserName.isEmpty() || mGroupName.isEmpty() ) - getUserGroupNames(); - - atom.m_uds = UDS_USER; - atom.m_str = mUserName; - entry.append(atom); - - atom.m_uds = UDS_GROUP; - atom.m_str = mGroupName; - entry.append(atom); - } - - if( mFlags & SSH2_FILEXFER_ATTR_PERMISSIONS ) { - atom.m_uds = UDS_ACCESS; - atom.m_long = mPermissions; - entry.append(atom); - - mode_t type = fileType(); - - // Set the type if we know what it is - if( type != 0 ) { - atom.m_uds = UDS_FILE_TYPE; - atom.m_long = (mLinkType ? mLinkType:type); - entry.append(atom); - } - - if( S_ISLNK(type) ) { - atom.m_uds = UDS_LINK_DEST; - atom.m_str = mLinkDestination; - entry.append(atom); - } - } - - return entry; -} - -/** Use to output the file attributes to a sftp packet */ -TQDataStream& operator<< (TQDataStream& s, const sftpFileAttr& fa) { - s << (TQ_UINT32)fa.mFlags; - - if( fa.mFlags & SSH2_FILEXFER_ATTR_SIZE ) - { s << (TQ_ULLONG)fa.mSize; } - - if( fa.mFlags & SSH2_FILEXFER_ATTR_UIDGID ) - { s << (TQ_UINT32)fa.mUid << (TQ_UINT32)fa.mGid; } - - if( fa.mFlags & SSH2_FILEXFER_ATTR_PERMISSIONS ) - { s << (TQ_UINT32)fa.mPermissions; } - - if( fa.mFlags & SSH2_FILEXFER_ATTR_ACMODTIME ) - { s << (TQ_UINT32)fa.mAtime << (TQ_UINT32)fa.mMtime; } - - if( fa.mFlags & SSH2_FILEXFER_ATTR_EXTENDED ) { - s << (TQ_UINT32)fa.mExtendedCount; - // XXX: Write extensions to data stream here - // s.writeBytes(extendedtype).writeBytes(extendeddata); - } - return s; -} - - -/** Use to read a file attribute from a sftp packet */ -TQDataStream& operator>> (TQDataStream& s, sftpFileAttr& fa) { - - // XXX Add some error checking in here in case - // we get a bad sftp packet. - - fa.clear(); - - if( fa.mDirAttrs ) { - TQCString fn; - s >> fn; - fn.truncate( fn.size() ); - - fa.mFilename = fa.mEncoding->decode( fn ); - - s >> fa.mLongname; - fa.mLongname.truncate( fa.mLongname.size() ); - //kdDebug() << ">>: ftpfileattr long filename (" << fa.mLongname.size() << ")= " << fa.mLongname << endl; - } - - s >> fa.mFlags; // get flags - - if( fa.mFlags & SSH2_FILEXFER_ATTR_SIZE ) { - TQ_ULLONG fileSize; - s >> fileSize; - fa.setFileSize(fileSize); - } - - TQ_UINT32 x; - - if( fa.mFlags & SSH2_FILEXFER_ATTR_UIDGID ) { - s >> x; fa.setUid(x); - s >> x; fa.setGid(x); - } - - if( fa.mFlags & SSH2_FILEXFER_ATTR_PERMISSIONS ) { - s >> x; fa.setPermissions(x); - } - - if( fa.mFlags & SSH2_FILEXFER_ATTR_ACMODTIME ) { - s >> x; fa.setAtime(x); - s >> x; fa.setMtime(x); - } - - if( fa.mFlags & SSH2_FILEXFER_ATTR_EXTENDED ) { - s >> x; fa.setExtendedCount(x); - // XXX: Read in extensions from data stream here - // s.readBytes(extendedtype).readBytes(extendeddata); - } - - fa.getUserGroupNames(); - return s; -} -/** Parse longname for the owner and group names. */ -void sftpFileAttr::getUserGroupNames(){ - // Get the name of the owner and group of the file from longname. - TQString user, group; - if( mLongname.isEmpty() ) { - // do not have the user name so use the user id instead - user.setNum(mUid); - group.setNum(mGid); - } - else { - int field = 0; - int i = 0; - int l = mLongname.length(); - - TQString longName = mEncoding->decode( mLongname ); - - kdDebug(7120) << "Decoded: " << longName << endl; - - // Find the beginning of the third field which contains the user name. - while( field != 2 ) { - if( longName[i].isSpace() ) { - field++; i++; - while( i < l && longName[i].isSpace() ) { i++; } - } - else { i++; } - } - // i is the index of the first character of the third field. - while( i < l && !longName[i].isSpace() ) { - user.append(longName[i]); - i++; - } - - // i is the first character of the space between fields 3 and 4 - // user contains the owner's user name - while( i < l && longName[i].isSpace() ) { - i++; - } - - // i is the first character of the fourth field - while( i < l && !longName[i].isSpace() ) { - group.append(longName[i]); - i++; - } - // group contains the name of the group. - } - - mUserName = user; - mGroupName = group; -} - -/** No descriptions */ -kdbgstream& operator<< (kdbgstream& s, sftpFileAttr& a) { - s << "Filename: " << a.mFilename - << ", Uid: " << a.mUid - << ", Gid: " << a.mGid - << ", Username: " << a.mUserName - << ", GroupName: " << a.mGroupName - << ", Permissions: " << a.mPermissions - << ", size: " << a.mSize - << ", atime: " << a.mAtime - << ", mtime: " << a.mMtime - << ", extended cnt: " << a.mExtendedCount; - - if (S_ISLNK(a.mLinkType)) { - s << ", Link Type: " << a.mLinkType; - s << ", Link Destination: " << a.mLinkDestination; - } - - return s; -} - -/** Make sure it builds with NDEBUG */ -kndbgstream& operator<< (kndbgstream& s, sftpFileAttr& ) { - return s; -} - -/** Clear all attributes and flags. */ -void sftpFileAttr::clear(){ - clearAtime(); - clearMtime(); - clearGid(); - clearUid(); - clearFileSize(); - clearPermissions(); - clearExtensions(); - mFilename = TQString::null; - mGroupName = TQString::null; - mUserName = TQString::null; - mLinkDestination = TQString::null; - mFlags = 0; - mLongname = "\0"; - mLinkType = 0; -} - -/** Return the size of the sftp attribute. */ -TQ_UINT32 sftpFileAttr::size() const{ - TQ_UINT32 size = 4; // for the attr flag - if( mFlags & SSH2_FILEXFER_ATTR_SIZE ) - size += 8; - - if( mFlags & SSH2_FILEXFER_ATTR_UIDGID ) - size += 8; - - if( mFlags & SSH2_FILEXFER_ATTR_PERMISSIONS ) - size += 4; - - if( mFlags & SSH2_FILEXFER_ATTR_ACMODTIME ) - size += 8; - - if( mFlags & SSH2_FILEXFER_ATTR_EXTENDED ) { - size += 4; - // add size of extensions - } - return size; -} - -/** Returns the file type as determined from the file permissions */ -mode_t sftpFileAttr::fileType() const{ - mode_t type = 0; - - if( S_ISLNK(mPermissions) ) - type |= S_IFLNK; - - if( S_ISREG(mPermissions) ) - type |= S_IFREG; - else if( S_ISDIR(mPermissions) ) - type |= S_IFDIR; - else if( S_ISCHR(mPermissions) ) - type |= S_IFCHR; - else if( S_ISBLK(mPermissions) ) - type |= S_IFBLK; - else if( S_ISFIFO(mPermissions) ) - type |= S_IFIFO; - else if( S_ISSOCK(mPermissions) ) - type |= S_IFSOCK; - - return type; -} - -void sftpFileAttr::setEncoding( KRemoteEncoding* encoding ) -{ - mEncoding = encoding; -} diff --git a/tdeioslave/sftp/sftpfileattr.h b/tdeioslave/sftp/sftpfileattr.h deleted file mode 100644 index 43b56979d..000000000 --- a/tdeioslave/sftp/sftpfileattr.h +++ /dev/null @@ -1,261 +0,0 @@ -/*************************************************************************** - sftpfileattr.h - description - ------------------- - begin : Sat Jun 30 2001 - copyright : (C) 2001 by Lucas Fisher - email : ljfisher@iastate.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. * - * * - ***************************************************************************/ - -#ifndef SFTPFILEATTR_H -#define SFTPFILEATTR_H - -#include - -#include -#include -#include - -#include -#include - -#include "sftp.h" - -/** - *@author Lucas Fisher - */ - -class KRemoteEncoding; - -class sftpFileAttr { - -private: // Private attributes - /** Name of file. */ - TQString mFilename; - - /** Specifies which fields of the file attribute are available. */ - TQ_UINT32 mFlags; - - /** Size of the file in bytes. Should be 64 bit safe. */ - TQ_ULLONG mSize; - - /** User id of the owner of the file. */ - uid_t mUid; - - /** Group id of the group to which the file belongs. */ - gid_t mGid; - - /** POSIX permissions of the file. */ - mode_t mPermissions; - - /** Last access time of the file in seconds from Jan 1, 1970. */ - time_t mAtime; - - /** Last modification time of file in seconds since Jan. 1, 1970. */ - time_t mMtime; - - /** Number of file attribute extensions. - Not currently implemented */ - TQ_UINT32 mExtendedCount; - - /** Longname of the file as found in a SSH_FXP_NAME sftp packet. - These contents are parse to return the file's owner name and - gr oup name. */ - TQCString mLongname; - - TQString mUserName; - TQString mGroupName; - - /** If file is a link, contains the destination of the link */ - TQString mLinkDestination; - - /** If resource is a link, contains the type the link,e.g. file,dir... */ - mode_t mLinkType; - - /** Whether >> operator should read filename and longname from the stream. */ - bool mDirAttrs; - - /** Holds the encoding of the remote host */ - KRemoteEncoding* mEncoding; - -public: - sftpFileAttr(); - - sftpFileAttr(KRemoteEncoding* encoding); - - ~sftpFileAttr(); - - /** Constructor to initialize the file attributes on declaration. */ - sftpFileAttr(TQ_ULLONG size_, uid_t uid_, gid_t gid_, mode_t permissions_, - time_t atime_, time_t mtime_, TQ_UINT32 extendedCount_ = 0); - - /** Return the size of the sftp attribute not including filename or longname*/ - TQ_UINT32 size() const; - - /** Clear all attributes and flags. */ - void clear(); - - /** Set the size of the file. */ - void setFileSize(TQ_ULLONG s) - { mSize = s; mFlags |= SSH2_FILEXFER_ATTR_SIZE; } - - /** The size file attribute will not be included in the UDSEntry - or when the file attribute is written to the sftp packet. */ - void clearFileSize() - { mSize = 0; mFlags &= ~SSH2_FILEXFER_ATTR_SIZE; } - - /** Returns the size of the file. */ - TQ_ULLONG fileSize() const { return mSize; } - - /** Sets the POSIX permissions of the file. */ - void setPermissions(mode_t p) - { mPermissions = p; mFlags |= SSH2_FILEXFER_ATTR_PERMISSIONS; } - - /** The permissions file attribute will not be included in the UDSEntry - or when the file attribute is written to the sftp packet. */ - void clearPermissions() - { mPermissions = 0; mFlags &= ~SSH2_FILEXFER_ATTR_PERMISSIONS; } - - /** Returns the POSIX permissons of the file. */ - mode_t permissions() const { return mPermissions; } - - /** Sets the group id of the file. */ - void setGid(gid_t id) - { mGid = id; mFlags |= SSH2_FILEXFER_ATTR_UIDGID; } - - /** Neither the gid or uid file attributes will not be included in the UDSEntry - or when the file attribute is written to the sftp packet. This is - equivalent to clearUid() */ - void clearGid() - { mGid = 0; mFlags &= SSH2_FILEXFER_ATTR_UIDGID; } - - /** Returns the group id of the file. */ - gid_t gid() const { return mGid; } - - /** Sets the uid of the file. */ - void setUid(uid_t id) - { mUid = id; mFlags |= SSH2_FILEXFER_ATTR_UIDGID; } - - /** Neither the gid or uid file attributes will not be included in the UDSEntry - or when the file attribute is written to the sftp packet. This is - equivalent to clearGid() */ - void clearUid() - { mUid = 0; mFlags &= SSH2_FILEXFER_ATTR_UIDGID; } - - /** Returns the user id of the file. */ - gid_t uid() const { return mUid; } - - /** Set the modificatoin time of the file in seconds since Jan. 1, 1970. */ - void setMtime(time_t t) - { mMtime = t; mFlags |= SSH2_FILEXFER_ATTR_ACMODTIME; } - - /** Neither the mtime or atime file attributes will not be included in the UDSEntry - or when the file attribute is written to the sftp packet. This is - equivalent to clearAtime() */ - void clearMtime() - { mMtime = 0; mFlags &= SSH2_FILEXFER_ATTR_ACMODTIME; } - - /** Returns the modification time of the file in seconds since Jan. 1, 1970. */ - time_t mtime() const { return mMtime; } - - /** Sets the access time of the file in seconds since Jan. 1, 1970. */ - void setAtime(time_t t) - { mAtime = t; mFlags |= SSH2_FILEXFER_ATTR_ACMODTIME; } - - /** Neither the atime or mtime file attributes will not be included in the UDSEntry - or when the file attribute is written to the sftp packet. This is - equivalent to clearMtime() */ - void clearAtime() - { mAtime = 0; mFlags &= SSH2_FILEXFER_ATTR_ACMODTIME; } - - /** Returns the last access time of the file in seconds since Jan. 1, 1970. */ - time_t atime() const { return mAtime; } - - /** Sets the number of file attribute extensions. */ - void setExtendedCount(unsigned int c) - { mExtendedCount = c; mFlags |= SSH2_FILEXFER_ATTR_EXTENDED; } - - /** No extensions will be included when the file attribute is written - to a sftp packet. */ - void clearExtensions() - { mExtendedCount = 0; mFlags &= ~SSH2_FILEXFER_ATTR_EXTENDED; } - - /** Returns the number of file attribute extentsions. */ - unsigned int extendedCount() const { return mExtendedCount; } - - /** Returns the flags for the sftp file attributes. */ - unsigned int flags() const { return mFlags; } - - /** Sets file's longname. See sftpFileAttr::longname. */ - void setLongname(TQString l) { mLongname = l.latin1(); } - - /** Returns a string describing the file attributes. The format is specific - to the implementation of the sftp server. In most cases (ie OpenSSH) - this is similar to the long output of 'ls'. */ - TQString longname() const { return mLongname; } - - void setLinkDestination(const TQString& target) - { mLinkDestination = target; } - - TQString linkDestination() - { return mLinkDestination; } - - /** Sets the actual type a symbolic link points to. */ - void setLinkType (mode_t type) { mLinkType = type; } - - mode_t linkType() const { return mLinkType; } - - /** No descriptions */ - void setFilename(const TQString& fn) - { mFilename = fn; } - - TQString filename() const - { return mFilename; } - - /** Returns a UDSEntry describing the file. - The UDSEntry is generated from the sftp file attributes. */ - TDEIO::UDSEntry entry(); - - /** Use to output the file attributes to a sftp packet - This will only write the sftp ATTR structure to the stream. - It will never write the filename and longname because the client - never sends those to the server. */ - friend TQDataStream& operator<< (TQDataStream&, const sftpFileAttr&); - - /** Use to read a file attribute from a sftp packet. - Read this carefully! If the DirAttrs flag is true, this will - read the filename, longname, and file attributes from the stream. - This is for use with listing directories. - If the DirAttrs flag is false, this will only read file attributes - from the stream. - BY DEFAULT, A NEW INSTANCE HAS DirAttrs == false */ - friend TQDataStream& operator>> (TQDataStream&, sftpFileAttr&); - - /** Parse longname for the owner and group names. */ - void getUserGroupNames(); - - /** Sets the DirAttrs flag. This flag affects how the >> operator works on data streams. */ - void setDirAttrsFlag(bool flag){ mDirAttrs = flag; } - - /** Gets the DirAttrs flag. */ - bool getDirAttrsFlag() const { return mDirAttrs; } - - friend kdbgstream& operator<< (kdbgstream& s, sftpFileAttr& a); - friend kndbgstream& operator<< (kndbgstream& s, sftpFileAttr& a); - - /** Returns the file type as determined from the file permissions */ - mode_t fileType() const; - - /** Set the encoding of the remote file system */ - void setEncoding( KRemoteEncoding* encoding ); -}; - -#endif diff --git a/tdeioslave/sftp/tdeio_sftp.cpp b/tdeioslave/sftp/tdeio_sftp.cpp index eab0eae42..9203f8537 100644 --- a/tdeioslave/sftp/tdeio_sftp.cpp +++ b/tdeioslave/sftp/tdeio_sftp.cpp @@ -1,62 +1,52 @@ -/*************************************************************************** - 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 kdDebug messages for debugging. We can't use a gdb -as described in the ioslave DEBUG.howto because tdeinit 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. -*/ - -#ifdef HAVE_CONFIG_H -#include -#endif + * Copyright (c) 2001 Lucas Fisher + * Copyright (c) 2009 Andreas Schneider + * Copyright (c) 2020 Martin Sandsmark + * KDE2 port + * Copyright (c) 2022 Mavridis Philippe + * Trinity port + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License (LGPL) as published by the Free Software Foundation; + * either version 2 of the License, or (at your option) any later + * version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public License + * along with this library; see the file COPYING.LIB. If not, write to + * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#include "tdeio_sftp.h" #include -#include -#include -#include -#include +#include #include -#include +#include #include #include -#include #include -#include #include -#include #include -#include #include +#include + #include #include #include -#include #include -#include #include #include -#include #include #include #include @@ -64,22 +54,21 @@ So we can't connect. #include #include #include -#include -#include +#include -#include "sftp.h" -#include "tdeio_sftp.h" -#include "atomicio.h" -#include "sftpfileattr.h" -#include "ksshprocess.h" +#include +#include +#include +#define TDEIO_SFTP_SPECIAL_TIMEOUT 30 +#define ZERO_STRUCTP(x) do { if ((x) != NULL) memset((char *)(x), 0, sizeof(*(x))); } while(0) using namespace TDEIO; extern "C" { - int KDE_EXPORT kdemain( int argc, char **argv ) + int kdemain( int argc, char **argv ) { - TDEInstance instance( "tdeio_sftp" ); + TDEInstance instance( "tdeio_sftp" ); kdDebug(TDEIO_SFTP_DB) << "*** Starting tdeio_sftp " << endl; @@ -87,8 +76,13 @@ extern "C" kdDebug(TDEIO_SFTP_DB) << "Usage: tdeio_sftp protocol domain-socket1 domain-socket2" << endl; exit(-1); } - sftpProtocol slave(argv[2], argv[3]); + + if (getenv("DEBUG_TDEIO_SFTP")) { + // Give us a coredump in the journal + signal(6, SIG_DFL); + } + slave.dispatchLoop(); kdDebug(TDEIO_SFTP_DB) << "*** tdeio_sftp Done" << endl; @@ -96,2187 +90,1684 @@ extern "C" } } - -/* - * 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; - } - - switch(errno) - { - case EINTR: - continue; - case EPIPE: - return ERR_CONNECTION_BROKEN; - case ENOSPC: - return ERR_DISK_FULL; - default: - return ERR_COULD_NOT_WRITE; - } +// 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; } - return 0; -} - -sftpProtocol::sftpProtocol(const TQCString &pool_socket, const TQCString &app_socket) - : SlaveBase("tdeio_sftp", pool_socket, app_socket), - mConnected(false), mPort(-1), mMsgId(0) { - kdDebug(TDEIO_SFTP_DB) << "sftpProtocol(): pid = " << getpid() << endl; -} + sftpProtocol *slave = (sftpProtocol *) userdata; -sftpProtocol::~sftpProtocol() { - kdDebug(TDEIO_SFTP_DB) << "~sftpProtocol(): pid = " << getpid() << endl; - closeConnection(); -} - -/** - * 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: - kdDebug(TDEIO_SFTP_DB) << "isSupportedOperation(type:" - << type << "): unrecognized operation type" << endl; - break; + if (slave->auth_callback(prompt, buf, len, echo, verify, userdata) < 0) { + return -1; } - return false; -} - -void sftpProtocol::copy(const KURL &src, const KURL &dest, int permissions, bool overwrite) -{ - kdDebug(TDEIO_SFTP_DB) << "copy(): " << src << " -> " << dest << endl; - - bool srcLocal = src.isLocalFile(); - bool destLocal = dest.isLocalFile(); - - if ( srcLocal && !destLocal ) // Copy file -> sftp - sftpCopyPut(src, dest, permissions, overwrite); - else if ( destLocal && !srcLocal ) // Copy sftp -> file - sftpCopyGet(dest, src, permissions, overwrite); - else - error(ERR_UNSUPPORTED_ACTION, TQString::null); + return 0; } -void sftpProtocol::sftpCopyGet(const KURL& dest, const KURL& src, int mode, bool overwrite) -{ - kdDebug(TDEIO_SFTP_DB) << "sftpCopyGet(): " << src << " -> " << dest << endl; - - // Attempt to establish a connection... - openConnection(); - if( !mConnected ) - return; - - KDE_struct_stat buff_orig; - TQCString dest_orig ( TQFile::encodeName(dest.path()) ); - bool origExists = (KDE_lstat( dest_orig.data(), &buff_orig ) != -1); - - if (origExists) - { - if (S_ISDIR(buff_orig.st_mode)) - { - error(ERR_IS_DIRECTORY, dest.prettyURL()); - return; - } - - if (!overwrite) - { - error(ERR_FILE_ALREADY_EXIST, dest.prettyURL()); - return; - } - } - - TDEIO::filesize_t offset = 0; - TQCString dest_part ( dest_orig + ".part" ); - - int fd = -1; - bool partExists = false; - bool markPartial = config()->readBoolEntry("MarkPartial", true); - - if (markPartial) - { - KDE_struct_stat buff_part; - partExists = (KDE_stat( dest_part.data(), &buff_part ) != -1); - - if (partExists && buff_part.st_size > 0 && S_ISREG(buff_part.st_mode)) - { - if (canResume( buff_part.st_size )) - { - offset = buff_part.st_size; - kdDebug(TDEIO_SFTP_DB) << "sftpCopyGet: Resuming @ " << offset << endl; - } - } - - if (offset > 0) - { - fd = KDE_open(dest_part.data(), O_RDWR); - offset = KDE_lseek(fd, 0, SEEK_END); - if (offset == 0) - { - error(ERR_CANNOT_RESUME, dest.prettyURL()); - return; - } - } - 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.data(), 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.data(), openFlags, initialMode); - } - - if(fd == -1) - { - kdDebug(TDEIO_SFTP_DB) << "sftpCopyGet: Unable to open (" << fd << ") for writting." << endl; - if (errno == EACCES) - error (ERR_WRITE_ACCESS_DENIED, dest.prettyURL()); - else - error (ERR_CANNOT_OPEN_FOR_WRITING, dest.prettyURL()); - return; - } - - Status info = sftpGet(src, offset, fd); - if ( info.code != 0 ) - { - // Should we keep the partially downloaded file ?? - TDEIO::filesize_t size = config()->readNumEntry("MinimumKeepSize", DEFAULT_MINIMUM_KEEP_SIZE); - if (info.size < size) - ::remove(dest_part.data()); - - error(info.code, info.text); - return; - } +void log_callback(ssh_session session, int priority, const char *message, + void *userdata) { + if (userdata == NULL) { + return; + } - if (::close(fd) != 0) - { - error(ERR_COULD_NOT_WRITE, dest.prettyURL()); - return; - } + sftpProtocol *slave = (sftpProtocol *) userdata; - // - if (markPartial) - { - if (::rename(dest_part.data(), dest_orig.data()) != 0) - { - error (ERR_CANNOT_RENAME_PARTIAL, dest_part); - return; - } - } - - data(TQByteArray()); - kdDebug(TDEIO_SFTP_DB) << "sftpCopyGet(): emit finished()" << endl; - finished(); + slave->log_callback(session, priority, message, userdata); } -sftpProtocol::Status sftpProtocol::sftpGet( const KURL& src, TDEIO::filesize_t offset, int fd ) -{ - int code; - sftpFileAttr attr(remoteEncoding()); - - Status res; - res.code = 0; - res.size = 0; +int sftpProtocol::auth_callback(const char *prompt, char *buf, size_t len, + int echo, int verify, void *userdata) { + TQString i_prompt = TQString::fromUtf8(prompt); - kdDebug(TDEIO_SFTP_DB) << "sftpGet(): " << src << endl; + // unused variables + (void) echo; + (void) verify; + (void) userdata; - // stat the file first to get its size - if( (code = sftpStat(src, attr)) != SSH2_FX_OK ) { - return doProcessStatus(code, src.prettyURL()); - } + kdDebug(TDEIO_SFTP_DB) << "Entering authentication callback, prompt=" << i_prompt << endl; - // 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; - } + TDEIO::AuthInfo info; - TDEIO::filesize_t fileSize = attr.fileSize(); - TQ_UINT32 pflags = SSH2_FXF_READ; - attr.clear(); + info.url.setProtocol("sftp"); + info.url.setHost(mHost); + info.url.setPort(mPort); + info.url.setUser(mUsername); - TQByteArray handle; - if( (code = sftpOpen(src, pflags, attr, handle)) != SSH2_FX_OK ) { - res.text = src.prettyURL(); - res.code = ERR_CANNOT_OPEN_FOR_READING; - return res; - } + info.comment = "sftp://" + mUsername + "@" + mHost; + info.username = i_prompt; + info.readOnly = true; + info.prompt = i_prompt; + info.keepPassword = false; // don't save passwords for public key, + // that's the task of ssh-agent. - // needed for determining mimetype - // note: have to emit mimetype before emitting totalsize. - TQByteArray buff; - TQByteArray mimeBuffer; - - unsigned int oldSize; - bool foundMimetype = false; - - // 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. - TQ_UINT32 len = 60*1024; - code = SSH2_FX_OK; - - kdDebug(TDEIO_SFTP_DB) << "sftpGet(): offset = " << offset << endl; - while( code == SSH2_FX_OK ) { - if( (code = sftpRead(handle, offset, len, buff)) == SSH2_FX_OK ) { - offset += buff.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 - KMimeMagicResult* result = - KMimeMagic::self()->findBufferFileType(mimeBuffer, src.fileName()); - kdDebug(TDEIO_SFTP_DB) << "sftpGet(): mimetype is " << - result->mimeType() << endl; - mimeType(result->mimeType()); - - // 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); - } - } + if (!openPassDlg(info)) { + kdDebug(TDEIO_SFTP_DB) << "Password dialog failed" << endl; + return -1; + } - /* - 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; - } - } + strncpy(buf, info.password.utf8().data(), len - 1); - if( code != SSH2_FX_EOF ) { - res.text = src.prettyURL(); - res.code = ERR_COULD_NOT_READ; // return here or still send empty array to indicate end of read? - } + info.password.fill('x'); - res.size = offset; - sftpClose(handle); - processedSize (offset); - return res; + return 0; } -void sftpProtocol::get(const KURL& url) { - kdDebug(TDEIO_SFTP_DB) << "get(): " << url << endl ; +void sftpProtocol::log_callback(ssh_session session, int priority, + const char *message, void *userdata) { + (void) session; + (void) userdata; - openConnection(); - if( !mConnected ) - return; - - // Get resume offset - TQ_UINT64 offset = config()->readUnsignedLongNumEntry("resume"); - if( offset > 0 ) { - canResume(); - kdDebug(TDEIO_SFTP_DB) << "get(): canResume(), offset = " << offset << endl; - } - - Status info = sftpGet(url, offset); - - if (info.code != 0) - { - error(info.code, info.text); - return; - } - - data(TQByteArray()); - kdDebug(TDEIO_SFTP_DB) << "get(): emit finished()" << endl; - finished(); + kdDebug(TDEIO_SFTP_DB) << "[" << priority << "] " << message << endl; } +int sftpProtocol::authenticateKeyboardInteractive(AuthInfo &info) { + TQString name, instruction, prompt; + int err = SSH_AUTH_ERROR; -void sftpProtocol::setHost (const TQString& h, int port, const TQString& user, const TQString& pass) -{ - kdDebug(TDEIO_SFTP_DB) << "setHost(): " << user << "@" << h << ":" << port << endl; - - if( mHost != h || mPort != port || user != mUsername || mPassword != pass ) - closeConnection(); - - mHost = h; - - if( port > 0 ) - mPort = port; - else { - mPort = -1; - } + kdDebug(TDEIO_SFTP_DB) << "Entering keyboard interactive function" << endl; - mUsername = user; - mPassword = pass; + err = ssh_userauth_kbdint(mSession, mUsername.utf8().data(), NULL); + while (err == SSH_AUTH_INFO) { + int n = 0; + int i = 0; - if (user.isEmpty()) - { - KUser u; - mUsername = u.loginName(); - } -} + name = TQString::fromUtf8(ssh_userauth_kbdint_getname(mSession)); + instruction = TQString::fromUtf8(ssh_userauth_kbdint_getinstruction(mSession)); + n = ssh_userauth_kbdint_getnprompts(mSession); + kdDebug(TDEIO_SFTP_DB) << "name=" << name << " instruction=" << instruction + << " prompts" << n << endl; -void sftpProtocol::openConnection() { + for (i = 0; i < n; ++i) { + char echo; + const char *answer = ""; - if(mConnected) - return; + prompt = TQString::fromUtf8(ssh_userauth_kbdint_getprompt(mSession, i, &echo)); + kdDebug(TDEIO_SFTP_DB) << "prompt=" << prompt << " echo=" << TQString::number(echo) << endl; + if (echo) { + // See RFC4256 Section 3.3 User Interface + TQString newPrompt; + TDEIO::AuthInfo infoKbdInt; - kdDebug(TDEIO_SFTP_DB) << "openConnection(): " << mUsername << "@" - << mHost << ":" << mPort << endl; + infoKbdInt.url.setProtocol("sftp"); + infoKbdInt.url.setHost(mHost); + infoKbdInt.url.setPort(mPort); - infoMessage( i18n("Opening SFTP connection to host %1:%2").arg(mHost).arg(mPort)); + infoKbdInt.caption = i18n("SFTP Login"); + infoKbdInt.comment = "sftp://" + mUsername + "@" + mHost; - if( mHost.isEmpty() ) { - kdDebug(TDEIO_SFTP_DB) << "openConnection(): Need hostname..." << endl; - error(ERR_UNKNOWN_HOST, i18n("No hostname specified")); - return; - } - - //////////////////////////////////////////////////////////////////////////// - // Setup AuthInfo for use with password caching and the - // password dialog box. - AuthInfo info; - info.url.setProtocol("sftp"); - info.url.setHost(mHost); - info.url.setPort(mPort); - info.url.setUser(mUsername); - info.caption = i18n("SFTP Login"); - info.comment = "sftp://" + mHost + ":" + TQString::number(mPort); - info.commentLabel = i18n("site:"); - info.username = mUsername; - info.keepPassword = true; - - /////////////////////////////////////////////////////////////////////////// - // Check for cached authentication info if a username AND password were - // not specified in setHost(). - if( mUsername.isEmpty() && mPassword.isEmpty() ) { - kdDebug(TDEIO_SFTP_DB) << "openConnection(): checking cache " - << "info.username = " << info.username - << ", info.url = " << info.url.prettyURL() << endl; - - if( checkCachedAuthentication(info) ) { - mUsername = info.username; - mPassword = info.password; + if (!name.isEmpty()) { + infoKbdInt.caption = TQString(i18n("SFTP Login") + " - " + name); } - } - - /////////////////////////////////////////////////////////////////////////// - // 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; - -// opt.opt = KSshProcess::SSH_VERBOSE; -// opts.append(opt); -// opts.append(opt); - - if( mPort != -1 ) { - opt.opt = KSshProcess::SSH_PORT; - opt.num = mPort; - opts.append(opt); - } - - opt.opt = KSshProcess::SSH_SUBSYSTEM; - opt.str = "sftp"; - opts.append(opt); - - opt.opt = KSshProcess::SSH_FORWARDX11; - opt.boolean = false; - opts.append(opt); - - opt.opt = KSshProcess::SSH_FORWARDAGENT; - opt.boolean = false; - opts.append(opt); - - opt.opt = KSshProcess::SSH_PROTOCOL; - opt.num = 2; - opts.append(opt); - - 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; - usernameIt = opts.append(opt); - } - - if( !mPassword.isEmpty() ) { - opt.opt = KSshProcess::SSH_PASSWD; - opt.str = mPassword; - passwdIt = opts.append(opt); - } - - ssh.setOptions(opts); - ssh.printArgs(); - - /////////////////////////////////////////////////////////////////////////// - // Start the ssh connection process. - // - - int err; // error code from KSshProcess - TQString msg; // msg for dialog box - TQString caption; // dialog box caption - bool firstTime = true; - bool dlgResult; - - while( !(mConnected = ssh.connect()) ) { - err = ssh.error(); - kdDebug(TDEIO_SFTP_DB) << "openConnection(): " - "Got " << err << " from KSshProcess::connect()" << endl; - - 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."); - - kdDebug(TDEIO_SFTP_DB) << "openConnection(): info.username = " << info.username - << ", info.url = " << info.url.prettyURL() << endl; - - if( firstTime ) - dlgResult = openPassDlg(info); - else - dlgResult = openPassDlg(info, i18n("Incorrect username or password")); - - if( dlgResult ) { - if( info.username.isEmpty() || info.password.isEmpty() ) { - error(ERR_COULD_NOT_AUTHENTICATE, - i18n("Please enter a username and password")); - continue; - } - } - else { - // user canceled or dialog failed to open - error(ERR_USER_CANCELED, TQString::null); - kdDebug(TDEIO_SFTP_DB) << "openConnection(): user canceled, dlgResult = " << dlgResult << endl; - closeConnection(); - return; - } - - firstTime = false; - - // 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 ) { - kdDebug(TDEIO_SFTP_DB) << "openConnection(): Username changed from " - << mUsername << " to " << info.username << endl; - - ssh.disconnect(); - - // 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() ) { - kdDebug(TDEIO_SFTP_DB) << "openConnection(): " - "Adding username to options list" << endl; - opt.opt = KSshProcess::SSH_USERNAME; - usernameIt = opts.append(opt); - } - - if( passwdIt == KSshProcess::SshOptListIterator() ) { - kdDebug(TDEIO_SFTP_DB) << "openConnection(): " - "Adding password to options list" << endl; - opt.opt = KSshProcess::SSH_PASSWD; - passwdIt = opts.append(opt); - } - - (*usernameIt).str = info.username; - (*passwdIt).str = info.password; - ssh.setOptions(opts); - ssh.printArgs(); - } - else { // just set the password - ssh.setPassword(info.password); - } - - mUsername = info.username; - mPassword = info.password; - - break; - - 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, TQString::null); - return; - } - ssh.acceptHostKey(true); - break; - - 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, TQString::null); - 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(); - messageBox(Information, msg, caption); - closeConnection(); - error(ERR_COULD_NOT_LOGIN, msg); - 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.")); - caption = i18n("Unexpected SFTP error: %1").arg(err); - msg = ssh.errorMsg(); - messageBox(Information, msg, caption); - closeConnection(); - error(ERR_UNKNOWN, msg); - return; + if (!instruction.isEmpty()) { + newPrompt = instruction + "\n\n"; } - } - // catch all in case we did something wrong above - if( !mConnected ) { - error(ERR_INTERNAL, TQString::null); - return; - } + newPrompt.append(prompt + "\n\n"); + infoKbdInt.readOnly = false; + infoKbdInt.keepPassword = false; + infoKbdInt.prompt = i18n("Use the username input field to answer this question."); - // Now send init packet. - kdDebug(TDEIO_SFTP_DB) << "openConnection(): Sending SSH2_FXP_INIT packet." << endl; - TQByteArray p; - TQDataStream packet(p, IO_WriteOnly); - packet << (TQ_UINT32)5; // packet length - packet << (TQ_UINT8) SSH2_FXP_INIT; // packet type - packet << (TQ_UINT32)SSH2_FILEXFER_VERSION; // client version - - putPacket(p); - getPacket(p); - - TQDataStream s(p, IO_ReadOnly); - TQ_UINT32 version; - TQ_UINT8 type; - s >> type; - kdDebug(TDEIO_SFTP_DB) << "openConnection(): Got type " << type << endl; - - if( type == SSH2_FXP_VERSION ) { - s >> version; - kdDebug(TDEIO_SFTP_DB) << "openConnection(): Got server version " << version << endl; - - // 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").arg(version)); - closeConnection(); - return; - } - } - else { - error(ERR_UNKNOWN, i18n("Protocol error.")); - closeConnection(); - return; - } - - // Login succeeded! - infoMessage(i18n("Successfully connected to %1").arg(mHost)); - info.url.setProtocol("sftp"); - info.url.setHost(mHost); - info.url.setPort(mPort); - info.url.setUser(mUsername); - info.username = mUsername; - info.password = mPassword; - kdDebug(TDEIO_SFTP_DB) << "sftpProtocol(): caching info.username = " << info.username << - ", info.url = " << info.url.prettyURL() << endl; - cacheAuthentication(info); - mConnected = true; - connected(); - - mPassword.fill('x'); - info.password.fill('x'); - - return; -} -void sftpProtocol::closeConnection() { - kdDebug(TDEIO_SFTP_DB) << "closeConnection()" << endl; - ssh.disconnect(); - mConnected = false; -} - -void sftpProtocol::sftpCopyPut(const KURL& src, const KURL& dest, int permissions, bool overwrite) { - - KDE_struct_stat buff; - TQCString file (TQFile::encodeName(src.path())); - - if (KDE_lstat(file.data(), &buff) == -1) { - error (ERR_DOES_NOT_EXIST, src.prettyURL()); - return; - } - - 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, false, overwrite, fd); - - // Close the file descriptor... - ::close( fd ); -} - -void sftpProtocol::sftpPut( const KURL& dest, int permissions, bool resume, bool overwrite, int fd ) { - - openConnection(); - if( !mConnected ) - return; - - kdDebug(TDEIO_SFTP_DB) << "sftpPut(): " << dest - << ", resume=" << resume - << ", overwrite=" << overwrite << endl; - - KURL origUrl( dest ); - sftpFileAttr origAttr(remoteEncoding()); - bool origExists = false; - - // Stat original (without part ext) to see if it already exists - int code = sftpStat(origUrl, origAttr); - - if( code == SSH2_FX_OK ) { - kdDebug(TDEIO_SFTP_DB) << "sftpPut(): already exists" << endl; - - // 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; + if (openPassDlg(infoKbdInt)) { + kdDebug(TDEIO_SFTP_DB) << "Got the answer from the password dialog" << endl; + answer = info.username.utf8().data(); } - } - else if( code != SSH2_FX_NO_SUCH_FILE ) { - processStatus(code, origUrl.prettyURL()); - return; - } - - // Do not waste time/resources with more remote stat calls if the file exists - // and we weren't instructed to overwrite it... - if( origExists && !overwrite ) { - error(ERR_FILE_ALREADY_EXIST, origUrl.prettyURL()); - return; - } - // Stat file with part ext to see if it already exists... - KURL partUrl( origUrl ); - partUrl.setFileName( partUrl.fileName() + ".part" ); - - TQ_UINT64 offset = 0; - bool partExists = false; - bool markPartial = config()->readBoolEntry("MarkPartial", true); - - if( markPartial ) { - - sftpFileAttr partAttr(remoteEncoding()); - code = sftpStat(partUrl, partAttr); - - if( code == SSH2_FX_OK ) { - kdDebug(TDEIO_SFTP_DB) << "sftpPut(): .part file already exists" << endl; - partExists = true; - offset = partAttr.fileSize(); - - // 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( !overwrite && !resume ) { - if (fd != -1) - resume = (KDE_lseek(fd, offset, SEEK_SET) != -1); - else - resume = canResume( offset ); - - kdDebug(TDEIO_SFTP_DB) << "sftpPut(): can resume = " << resume - << ", offset = " << offset; - - if( !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; + if (ssh_userauth_kbdint_setanswer(mSession, i, answer) < 0) { + kdDebug(TDEIO_SFTP_DB) << "An error occurred setting the answer: " + << ssh_get_error(mSession) << endl; + return SSH_AUTH_ERROR; } - } - - // Determine the url we will actually write to... - KURL writeUrl (markPartial ? partUrl:origUrl); - - TQ_UINT32 pflags = 0; - if( overwrite && !resume ) - pflags = SSH2_FXF_WRITE | SSH2_FXF_CREAT | SSH2_FXF_TRUNC; - else if( !overwrite && !resume ) - pflags = SSH2_FXF_WRITE | SSH2_FXF_CREAT | SSH2_FXF_EXCL; - else if( overwrite && resume ) - pflags = SSH2_FXF_WRITE | SSH2_FXF_CREAT; - else if( !overwrite && resume ) - pflags = SSH2_FXF_WRITE | SSH2_FXF_CREAT | SSH2_FXF_APPEND; - - sftpFileAttr attr(remoteEncoding()); - TQByteArray handle; - - // 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); - - code = sftpOpen( writeUrl, pflags, attr, handle ); - if( code != SSH2_FX_OK ) { - - // Rename the file back to its original name if a - // put fails due to permissions problems... - if( markPartial && overwrite ) { - (void) sftpRename(partUrl, origUrl); - writeUrl = origUrl; + break; + } else { + if (prompt.lower() == "password") { + answer = mPassword.utf8().data(); + } else { + info.readOnly = true; // set username readonly + info.prompt = prompt; + + if (openPassDlg(info)) { + kdDebug(TDEIO_SFTP_DB) << "Got the answer from the password dialog" << endl; + answer = info.password.utf8().data(); + } } - 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 (ssh_userauth_kbdint_setanswer(mSession, i, answer) < 0) { + kdDebug(TDEIO_SFTP_DB) << "An error occurred setting the answer: " + << ssh_get_error(mSession) << endl; + return SSH_AUTH_ERROR; } + } } + err = ssh_userauth_kbdint(mSession, mUsername.utf8().data(), NULL); + } - long nbytes; - TQByteArray buff; - - do { - - if( fd != -1 ) { - buff.resize( 16*1024 ); - if ( (nbytes = ::read(fd, buff.data(), buff.size())) > -1 ) - buff.resize( nbytes ); - } - else { - dataReq(); - nbytes = readData( buff ); - } - - if( nbytes >= 0 ) { - if( (code = sftpWrite(handle, offset, buff)) != SSH2_FX_OK ) { - error(ERR_COULD_NOT_WRITE, dest.prettyURL()); - return; - } - - offset += nbytes; - 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() ) { - sftpClose(handle); - closeConnection(); - error(ERR_UNKNOWN, i18n("An internal error occurred. Please try again.")); - return; - } - } + return err; +} - } while( nbytes > 0 ); +void sftpProtocol::reportError(const KURL &url, const int err) { + kdDebug(TDEIO_SFTP_DB) << "url = " << url.url() << " - err=" << err << endl; - if( nbytes < 0 ) { - sftpClose(handle); + switch (err) { + case SSH_FX_OK: + break; + case SSH_FX_NO_SUCH_FILE: + case SSH_FX_NO_SUCH_PATH: + error(TDEIO::ERR_DOES_NOT_EXIST, url.prettyURL()); + break; + case SSH_FX_PERMISSION_DENIED: + error(TDEIO::ERR_ACCESS_DENIED, url.prettyURL()); + break; + case SSH_FX_FILE_ALREADY_EXISTS: + error(TDEIO::ERR_FILE_ALREADY_EXIST, url.prettyURL()); + break; + case SSH_FX_INVALID_HANDLE: + error(TDEIO::ERR_MALFORMED_URL, url.prettyURL()); + break; + case SSH_FX_OP_UNSUPPORTED: + error(TDEIO::ERR_UNSUPPORTED_ACTION, url.prettyURL()); + break; + case SSH_FX_BAD_MESSAGE: + error(TDEIO::ERR_UNKNOWN, url.prettyURL()); + break; + default: + error(TDEIO::ERR_INTERNAL, url.prettyURL()); + break; + } +} - if( markPartial ) { - // Remove remote file if it smaller than our keep size - uint minKeepSize = config()->readNumEntry("MinimumKeepSize", DEFAULT_MINIMUM_KEEP_SIZE); +bool sftpProtocol::createUDSEntry(const TQString &filename, const TQByteArray &path, + UDSEntry &entry, short int details) { + mode_t type; + mode_t access; + char *link; - if( sftpStat(writeUrl, attr) == SSH2_FX_OK ) { - if( attr.fileSize() < minKeepSize ) { - sftpRemove(writeUrl, true); - } - } - } + ASSERT(entry.count() == 0); - error( ERR_UNKNOWN, i18n("Unknown error was encountered while copying the file " - "to '%1'. Please try again.").arg(dest.host()) ); - return; - } + sftp_attributes sb = sftp_lstat(mSftp, path.data()); + if (sb == NULL) { + return false; + } - if( (code = sftpClose(handle)) != SSH2_FX_OK ) { - error(ERR_COULD_NOT_WRITE, writeUrl.prettyURL()); - return; + UDSAtom atom; + atom.m_uds = UDS_NAME; + atom.m_str = filename; + entry.append(atom); + + if (sb->type == SSH_FILEXFER_TYPE_SYMLINK) { + atom.m_uds = UDS_FILE_TYPE; + atom.m_long = S_IFREG; + entry.append(atom); + link = sftp_readlink(mSftp, path.data()); + if (link == NULL) { + sftp_attributes_free(sb); + return false; + } + atom.m_uds = UDS_LINK_DEST; + atom.m_str = TQFile::decodeName(link); + entry.append(atom); + delete link; + // A symlink -> follow it only if details > 1 + if (details > 1) { + sftp_attributes sb2 = sftp_stat(mSftp, path.data()); + if (sb2 == NULL) { + // It is a link pointing to nowhere + type = S_IFMT - 1; + access = S_IRWXU | S_IRWXG | S_IRWXO; + atom.m_uds = UDS_FILE_TYPE; + atom.m_long = type; + entry.append(atom); + + atom.m_uds = UDS_ACCESS; + atom.m_long = access; + entry.append(atom); + + atom.m_uds = UDS_SIZE; + atom.m_long = 0LL; + entry.append(atom); + + goto notype; + } + sftp_attributes_free(sb); + sb = sb2; } + } - // 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; - } - } + switch (sb->type) { + case SSH_FILEXFER_TYPE_REGULAR: + atom.m_uds = UDS_FILE_TYPE; + atom.m_long = S_IFREG; + entry.append(atom); + break; + case SSH_FILEXFER_TYPE_DIRECTORY: + atom.m_uds = UDS_FILE_TYPE; + atom.m_long = S_IFDIR; + entry.append(atom); + break; + case SSH_FILEXFER_TYPE_SYMLINK: + atom.m_uds = UDS_FILE_TYPE; + atom.m_long = S_IFLNK; + entry.append(atom); + break; + case SSH_FILEXFER_TYPE_SPECIAL: + case SSH_FILEXFER_TYPE_UNKNOWN: + atom.m_uds = UDS_FILE_TYPE; + atom.m_long = S_IFMT - 1; + entry.append(atom); + break; + } - finished(); -} + access = sb->permissions & 07777; + atom.m_uds = UDS_ACCESS; + atom.m_long = access; + entry.append(atom); + + atom.m_uds = UDS_SIZE; + atom.m_long = sb->size; + entry.append(atom); + +notype: + if (details > 0) { + if (sb->owner) { + atom.m_uds = UDS_USER; + atom.m_str = TQString::fromUtf8(sb->owner); + entry.append(atom); + } else { + atom.m_uds = UDS_USER; + atom.m_str = TQString::number(sb->uid); + entry.append(atom); + } + + if (sb->group) { + atom.m_uds = UDS_GROUP; + atom.m_str = TQString::fromUtf8(sb->group); + entry.append(atom); + } else { + atom.m_uds = UDS_GROUP; + atom.m_str = TQString::number(sb->gid); + entry.append(atom); + } + atom.m_uds = UDS_ACCESS_TIME; + atom.m_long = sb->atime; + entry.append(atom); + + atom.m_uds = UDS_MODIFICATION_TIME; + atom.m_long = sb->mtime; + entry.append(atom); + + atom.m_uds = UDS_MODIFICATION_TIME; + atom.m_long = sb->createtime; + entry.append(atom); + } -void sftpProtocol::put ( const KURL& url, int permissions, bool overwrite, bool resume ){ - kdDebug(TDEIO_SFTP_DB) << "put(): " << url << ", overwrite = " << overwrite - << ", resume = " << resume << endl; + sftp_attributes_free(sb); - sftpPut( url, permissions, resume, overwrite ); + return true; } -void sftpProtocol::stat ( const KURL& url ){ - kdDebug(TDEIO_SFTP_DB) << "stat(): " << url << endl; +TQString sftpProtocol::canonicalizePath(const TQString &path) { + kdDebug(TDEIO_SFTP_DB) << "Path to canonicalize: " << path << endl; + TQString cPath; + char *sPath = NULL; - 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; - UDSAtom atom; - - atom.m_uds = TDEIO::UDS_NAME; - atom.m_str = TQString::null; - entry.append( atom ); - - atom.m_uds = TDEIO::UDS_FILE_TYPE; - atom.m_long = S_IFDIR; - entry.append( atom ); + if (path.isEmpty()) { + return cPath; + } - atom.m_uds = TDEIO::UDS_ACCESS; - atom.m_long = S_IRUSR | S_IXUSR | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH; - entry.append( atom ); + sPath = sftp_canonicalize_path(mSftp, path.utf8().data()); + if (sPath == NULL) { + kdDebug(TDEIO_SFTP_DB) << "Could not canonicalize path: " << path << endl; + return cPath; + } - atom.m_uds = TDEIO::UDS_USER; - atom.m_str = mUsername; - entry.append( atom ); - atom.m_uds = TDEIO::UDS_GROUP; - entry.append( atom ); + cPath = TQFile::decodeName(sPath); + delete sPath; - // no size - statEntry( entry ); - finished(); - return; - } + kdDebug(TDEIO_SFTP_DB) << "Canonicalized path: " << cPath << endl; - int code; - sftpFileAttr attr(remoteEncoding()); - if( (code = sftpStat(url, attr)) != SSH2_FX_OK ) { - processStatus(code, url.prettyURL()); - return; - } - else { - //kdDebug() << "We sent and received stat packet ok" << endl; - attr.setFilename(url.fileName()); - statEntry(attr.entry()); - } - - finished(); - - kdDebug(TDEIO_SFTP_DB) << "stat: END" << endl; - return; + return cPath; } +sftpProtocol::sftpProtocol(const TQCString &pool_socket, const TQCString &app_socket) + : SlaveBase("tdeio_sftp", pool_socket, app_socket), + mConnected(false), mPort(-1), mSession(NULL), mSftp(NULL) { +#ifndef Q_WS_WIN + kdDebug(TDEIO_SFTP_DB) << "pid = " << getpid() << endl; -void sftpProtocol::mimetype ( const KURL& url ){ - kdDebug(TDEIO_SFTP_DB) << "mimetype(): " << url << endl; + kdDebug(TDEIO_SFTP_DB) << "debug = " << getenv("TDEIO_SFTP_LOG_VERBOSITY") << endl; +#endif - openConnection(); - if( !mConnected ) - return; + mCallbacks = (ssh_callbacks) malloc(sizeof(struct ssh_callbacks_struct)); + if (mCallbacks == NULL) { + error(TDEIO::ERR_OUT_OF_MEMORY, i18n("Could not allocate callbacks")); + return; + } + ZERO_STRUCTP(mCallbacks); - TQ_UINT32 pflags = SSH2_FXF_READ; - TQByteArray handle, mydata; - sftpFileAttr attr(remoteEncoding()); - int code; - if( (code = sftpOpen(url, pflags, attr, handle)) != SSH2_FX_OK ) { - error(ERR_CANNOT_OPEN_FOR_READING, url.prettyURL()); - return; - } + mCallbacks->userdata = this; + mCallbacks->auth_function = ::auth_callback; + if (getenv("TDEIO_SFTP_LOG_VERBOSITY")) { + mCallbacks->log_function = ::log_callback; + } - TQ_UINT32 len = 1024; // Get first 1k for determining mimetype - TQ_UINT64 offset = 0; - code = SSH2_FX_OK; - while( offset < len && code == SSH2_FX_OK ) { - if( (code = sftpRead(handle, offset, len, mydata)) == SSH2_FX_OK ) { - data(mydata); - offset += mydata.size(); - processedSize(offset); + ssh_callbacks_init(mCallbacks); +} - kdDebug(TDEIO_SFTP_DB) << "mimetype(): offset = " << offset << endl; - } - } +sftpProtocol::~sftpProtocol() { +#ifndef Q_WS_WIN + kdDebug(TDEIO_SFTP_DB) << "pid = " << getpid() << endl; +#endif + closeConnection(); + delete mCallbacks; - data(TQByteArray()); - processedSize(offset); - sftpClose(handle); - finished(); - kdDebug(TDEIO_SFTP_DB) << "mimetype(): END" << endl; + /* cleanup and shut down cryto stuff */ + ssh_finalize(); } +void sftpProtocol::setHost(const TQString& h, int port, const TQString& user, const TQString& pass) { + kdDebug(TDEIO_SFTP_DB) << "setHost(): " << user << "@" << h << ":" << port << endl; -void sftpProtocol::listDir(const KURL& url) { - kdDebug(TDEIO_SFTP_DB) << "listDir(): " << url << endl; - - openConnection(); - if( !mConnected ) - return; - - if( !url.hasPath() ) { - KURL newUrl ( url ); - if( sftpRealPath(url, newUrl) == SSH2_FX_OK ) { - kdDebug(TDEIO_SFTP_DB) << "listDir: Redirecting to " << newUrl << endl; - redirection(newUrl); - finished(); - return; - } - } + if (mConnected) { + closeConnection(); + } - int code; - TQByteArray handle; + mHost = h; - if( (code = sftpOpenDirectory(url, handle)) != SSH2_FX_OK ) { - kdError(TDEIO_SFTP_DB) << "listDir(): open directory failed" << endl; - processStatus(code, url.prettyURL()); - return; + if (port > 0) { + mPort = port; + } else { + struct servent *pse; + if ((pse = getservbyname("ssh", "tcp") ) == NULL) { + mPort = 22; + } else { + mPort = ntohs(pse->s_port); } + } + kdDebug(TDEIO_SFTP_DB) << "setHost(): mPort=" << mPort << endl; - 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()); - kdDebug(TDEIO_SFTP_DB) << "listDir(): return code = " << code << endl; - } - - if( (code = sftpClose(handle)) != SSH2_FX_OK ) { - kdError(TDEIO_SFTP_DB) << "listdir(): closing of directory failed" << endl; - processStatus(code, url.prettyURL()); - return; - } - - finished(); - kdDebug(TDEIO_SFTP_DB) << "listDir(): END" << endl; + mUsername = user; + mPassword = pass; } -/** 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){ - - kdDebug(TDEIO_SFTP_DB) << "mkdir() creating dir: " << url.path() << endl; - - openConnection(); - if( !mConnected ) - return; - - TQCString path = remoteEncoding()->encode(url.path()); - uint len = path.length(); - - sftpFileAttr attr(remoteEncoding()); - - if (permissions != -1) - attr.setPermissions(permissions); - - TQ_UINT32 id, expectedId; - id = expectedId = mMsgId++; - - TQByteArray p; - TQDataStream s(p, IO_WriteOnly); - s << TQ_UINT32(1 /*type*/ + 4 /*id*/ + 4 /*str length*/ + len + attr.size()); - s << (TQ_UINT8)SSH2_FXP_MKDIR; - s << id; - s.writeBytes(path.data(), len); - s << attr; - - kdDebug(TDEIO_SFTP_DB) << "mkdir(): packet size is " << p.size() << endl; - - putPacket(p); - getPacket(p); - - TQ_UINT8 type; - TQDataStream r(p, IO_ReadOnly); - - r >> type >> id; - if( id != expectedId ) { - kdError(TDEIO_SFTP_DB) << "mkdir: sftp packet id mismatch" << endl; - error(ERR_COULD_NOT_MKDIR, path); - finished(); - return; - } - - if( type != SSH2_FXP_STATUS ) { - kdError(TDEIO_SFTP_DB) << "mkdir(): unexpected packet type of " << type << endl; - error(ERR_COULD_NOT_MKDIR, path); - finished(); - return; - } +void sftpProtocol::openConnection() { - int code; - r >> code; - if( code != SSH2_FX_OK ) { - kdError(TDEIO_SFTP_DB) << "mkdir(): failed with code " << code << endl; - - // 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; - } + if (mConnected) { + return; + } - error(ERR_COULD_NOT_MKDIR, path); - } + kdDebug(TDEIO_SFTP_DB) << "username=" << mUsername << ", host=" << mHost << ", port=" << mPort << endl; - finished(); -} + infoMessage(i18n("Opening SFTP connection to host %1:%2").arg(mHost).arg(mPort)); -void sftpProtocol::rename(const KURL& src, const KURL& dest, bool overwrite){ - kdDebug(TDEIO_SFTP_DB) << "rename(" << src << " -> " << dest << ")" << endl; + if (mHost.isEmpty()) { + kdDebug(TDEIO_SFTP_DB) << "openConnection(): Need hostname..." << endl; + error(TDEIO::ERR_UNKNOWN_HOST, i18n("No hostname specified.")); + return; + } - if (!isSupportedOperation(SSH2_FXP_RENAME)) { - error(ERR_UNSUPPORTED_ACTION, - i18n("The remote host does not support renaming files.")); - return; + // Setup AuthInfo for use with password caching and the + // password dialog box. + AuthInfo info; + + info.url.setProtocol("sftp"); + info.url.setHost(mHost); + info.url.setPort(mPort); + info.url.setUser(mUsername); + info.caption = i18n("SFTP Login"); + info.comment = "sftp://" + mHost + ':' + TQString::number(mPort); + info.commentLabel = i18n("site:"); + info.username = mUsername; + info.keepPassword = true; // make the "keep Password" check box visible to the user. + + // Check for cached authentication info if no password is specified... + if (mPassword.isEmpty()) { + kdDebug(TDEIO_SFTP_DB) << "checking cache: info.username = " << info.username + << ", info.url = " << info.url.prettyURL() << endl; + + if (checkCachedAuthentication(info)) { + mUsername = info.username; + mPassword = info.password; } + } - openConnection(); - if( !mConnected ) - return; + // Start the ssh connection. + TQString msg; // msg for dialog box + TQString caption; // dialog box caption + unsigned char *hash = NULL; // the server hash + char *hexa; + char *verbosity; + int rc, state; + int timeout_sec = 30, timeout_usec = 0; + + mSession = ssh_new(); + if (mSession == NULL) { + error(TDEIO::ERR_INTERNAL, i18n("Could not create a new SSH session.")); + return; + } - // Always stat the destination before attempting to rename - // a file or a directory... - sftpFileAttr attr(remoteEncoding()); - int code = sftpStat(dest, attr); + kdDebug(TDEIO_SFTP_DB) << "Creating the SSH session and setting options" << endl; - // 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 (!overwrite) - { - if ( S_ISDIR(attr.permissions()) ) - error( TDEIO::ERR_DIR_ALREADY_EXIST, dest.url() ); - else - error( TDEIO::ERR_FILE_ALREADY_EXIST, dest.url() ); - return; - } - - // 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; - } - } + // Set timeout + rc = ssh_options_set(mSession, SSH_OPTIONS_TIMEOUT, &timeout_sec); + if (rc < 0) { + kdDebug(TDEIO_SFTP_DB) << "Could not set a timeout."; + } + rc = ssh_options_set(mSession, SSH_OPTIONS_TIMEOUT_USEC, &timeout_usec); + if (rc < 0) { + kdDebug(TDEIO_SFTP_DB) << "Could not set a timeout in usec."; + } - // Do the renaming... - if( (code = sftpRename(src, dest)) != SSH2_FX_OK ) { - processStatus(code); - return; - } + // Don't use any compression + rc = ssh_options_set(mSession, SSH_OPTIONS_COMPRESSION_C_S, "none"); + if (rc < 0) { + kdDebug(TDEIO_SFTP_DB) << "Could not set compression client <- server."; + } - finished(); - kdDebug(TDEIO_SFTP_DB) << "rename(): END" << endl; -} + rc = ssh_options_set(mSession, SSH_OPTIONS_COMPRESSION_S_C, "none"); + if (rc < 0) { + kdDebug(TDEIO_SFTP_DB) << "Could not set compression server -> client."; + } -void sftpProtocol::symlink(const TQString& target, const KURL& dest, bool overwrite){ - kdDebug(TDEIO_SFTP_DB) << "symlink()" << endl; + // Set host and port + rc = ssh_options_set(mSession, SSH_OPTIONS_HOST, mHost.utf8().data()); + if (rc < 0) { + error(TDEIO::ERR_OUT_OF_MEMORY, i18n("Could not set host.")); + return; + } - if (!isSupportedOperation(SSH2_FXP_SYMLINK)) { - error(ERR_UNSUPPORTED_ACTION, - i18n("The remote host does not support creating symbolic links.")); + if (mPort > 0) { + rc = ssh_options_set(mSession, SSH_OPTIONS_PORT, &mPort); + if (rc < 0) { + error(TDEIO::ERR_OUT_OF_MEMORY, i18n("Could not set port.")); return; } + } - openConnection(); - if( !mConnected ) - return; - - int code; - bool failed = false; - if( (code = sftpSymLink(target, dest)) != SSH2_FX_OK ) { - if( 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; - } - } - } - else if( code == SSH2_FX_FAILURE ) { - error(ERR_FILE_ALREADY_EXIST, dest.prettyURL()); - return; - } - else - failed = true; - } - - // 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); - - finished(); -} - -void sftpProtocol::chmod(const KURL& url, int permissions){ - TQString perms; - perms.setNum(permissions, 8); - kdDebug(TDEIO_SFTP_DB) << "chmod(" << url << ", " << perms << ")" << endl; - - openConnection(); - if( !mConnected ) - return; - - sftpFileAttr attr(remoteEncoding()); - - if (permissions != -1) - attr.setPermissions(permissions); - - int code; - if( (code = sftpSetStat(url, attr)) != SSH2_FX_OK ) { - kdError(TDEIO_SFTP_DB) << "chmod(): sftpSetStat failed with error " << code << endl; - if( code == SSH2_FX_FAILURE ) - error(ERR_CANNOT_CHMOD, TQString::null); - else - processStatus(code, url.prettyURL()); + // Set the username + if (!mUsername.isEmpty()) { + rc = ssh_options_set(mSession, SSH_OPTIONS_USER, mUsername.utf8().data()); + if (rc < 0) { + error(TDEIO::ERR_OUT_OF_MEMORY, i18n("Could not set username.")); + return; } - finished(); -} - - -void sftpProtocol::del(const KURL &url, bool isfile){ - kdDebug(TDEIO_SFTP_DB) << "del(" << url << ", " << (isfile?"file":"dir") << ")" << endl; - - openConnection(); - if( !mConnected ) - return; + } - int code; - if( (code = sftpRemove(url, isfile)) != SSH2_FX_OK ) { - kdError(TDEIO_SFTP_DB) << "del(): sftpRemove failed with error code " << code << endl; - processStatus(code, url.prettyURL()); + verbosity = getenv("TDEIO_SFTP_LOG_VERBOSITY"); + if (verbosity) { + rc = ssh_options_set(mSession, SSH_OPTIONS_LOG_VERBOSITY_STR, verbosity); + if (rc < 0) { + error(TDEIO::ERR_OUT_OF_MEMORY, i18n("Could not set log verbosity.")); + return; } - finished(); -} + } -void sftpProtocol::slave_status() { - kdDebug(TDEIO_SFTP_DB) << "slave_status(): connected to " - << mHost << "? " << mConnected << endl; + // Read ~/.ssh/config + rc = ssh_options_parse_config(mSession, NULL); + if (rc < 0) { + error(TDEIO::ERR_INTERNAL, i18n("Could not parse the config file.")); + return; + } - slaveStatus ((mConnected ? mHost : TQString::null), mConnected); -} + ssh_set_callbacks(mSession, mCallbacks); -bool sftpProtocol::getPacket(TQByteArray& msg) { - TQByteArray buf(4096); + kdDebug(TDEIO_SFTP_DB) << "Trying to connect to the SSH server" << endl; - // Get the message length... - ssize_t len = atomicio(ssh.stdioFd(), buf.data(), 4, true /*read*/); + /* try to connect */ + rc = ssh_connect(mSession); + if (rc < 0) { + error(TDEIO::ERR_COULD_NOT_CONNECT, TQString::fromUtf8(ssh_get_error(mSession))); + closeConnection(); + return; + } - if( len == 0 || len == -1 ) { - kdDebug(TDEIO_SFTP_DB) << "getPacket(): read of packet length failed, ret = " - << len << ", error =" << strerror(errno) << endl; - closeConnection(); - error( ERR_CONNECTION_BROKEN, mHost); - msg.resize(0); - return false; - } + kdDebug(TDEIO_SFTP_DB) << "Getting the SSH server hash" << endl; - uint msgLen; - TQDataStream s(buf, IO_ReadOnly); - s >> msgLen; + /* get the hash */ + ssh_key serverKey; + if (ssh_get_server_publickey(mSession, &serverKey) < 0) { + error(TDEIO::ERR_COULD_NOT_CONNECT, TQString::fromUtf8(ssh_get_error(mSession))); + closeConnection(); + return; + } - //kdDebug(TDEIO_SFTP_DB) << "getPacket(): Message size = " << msgLen << endl; + size_t hlen; + if (ssh_get_publickey_hash(serverKey, SSH_PUBLICKEY_HASH_SHA256, &hash, &hlen) < 0) { + error(TDEIO::ERR_COULD_NOT_CONNECT, TQString::fromUtf8(ssh_get_error(mSession))); + closeConnection(); + return; + } - msg.resize(0); + kdDebug(TDEIO_SFTP_DB) << "Checking if the SSH server is known" << endl; - TQBuffer b( msg ); - b.open( IO_WriteOnly ); + /* check the server public key hash */ + state = ssh_session_is_known_server(mSession); + switch (state) { + case SSH_KNOWN_HOSTS_OK: + break; + case SSH_KNOWN_HOSTS_OTHER: + delete hash; + error(TDEIO::ERR_CONNECTION_BROKEN, i18n("The host key for this server was " + "not found, but another type of key exists.\n" + "An attacker might change the default server key to confuse your " + "client into thinking the key does not exist.\n" + "Please contact your system administrator.\n%1").arg(TQString::fromUtf8(ssh_get_error(mSession)))); + closeConnection(); + return; + case SSH_SERVER_KNOWN_CHANGED: + hexa = ssh_get_hexa(hash, hlen); + delete hash; + /* TODO print known_hosts file, port? */ + error(TDEIO::ERR_CONNECTION_BROKEN, i18n("The host key for the server %1 has changed.\n" + "This could either mean that DNS SPOOFING is happening or the IP " + "address for the host and its host key have changed at the same time.\n" + "The fingerprint for the key sent by the remote host is:\n %2\n" + "Please contact your system administrator.\n%3").arg( + mHost).arg(TQString::fromUtf8(hexa)).arg(TQString::fromUtf8(ssh_get_error(mSession)))); + delete hexa; + closeConnection(); + return; + case SSH_KNOWN_HOSTS_NOT_FOUND: + case SSH_KNOWN_HOSTS_UNKNOWN: + hexa = ssh_get_hexa(hash, hlen); + delete hash; + caption = i18n("Warning: Cannot verify host's identity."); + msg = i18n("The authenticity of host %1 cannot be established.\n" + "The key fingerprint is: %2\n" + "Are you sure you want to continue connecting?").arg(mHost).arg(hexa); + delete hexa; + + if (KMessageBox::Yes != messageBox(WarningYesNo, msg, caption)) { + closeConnection(); + error(TDEIO::ERR_USER_CANCELED, TQString()); + return; + } - while( msgLen ) { - len = atomicio(ssh.stdioFd(), buf.data(), kMin((uint)buf.size(), msgLen), true /*read*/); + /* write the known_hosts file */ + kdDebug(TDEIO_SFTP_DB) << "Adding server to known_hosts file." << endl; + if (ssh_session_update_known_hosts(mSession) != SSH_OK) { + error(TDEIO::ERR_USER_CANCELED, TQString::fromUtf8(ssh_get_error(mSession))); + closeConnection(); + return; + } + break; + case SSH_KNOWN_HOSTS_ERROR: + delete hash; + error(TDEIO::ERR_COULD_NOT_CONNECT, TQString::fromUtf8(ssh_get_error(mSession))); + return; + } - if( len == 0 || len == -1) { - TQString errmsg; - if (len == 0) - errmsg = i18n("Connection closed"); - else - errmsg = i18n("Could not read SFTP packet"); - kdDebug(TDEIO_SFTP_DB) << "getPacket(): nothing to read, ret = " << - len << ", error =" << strerror(errno) << endl; - closeConnection(); - error(ERR_CONNECTION_BROKEN, errmsg); - b.close(); - return false; - } + kdDebug(TDEIO_SFTP_DB) << "Trying to authenticate with the server" << endl; - b.writeBlock(buf.data(), len); + // Try to authenticate + rc = ssh_userauth_none(mSession, NULL); + if (rc == SSH_AUTH_ERROR) { + closeConnection(); + error(TDEIO::ERR_COULD_NOT_LOGIN, i18n("Authentication failed.")); + return; + } - //kdDebug(TDEIO_SFTP_DB) << "getPacket(): Read Message size = " << len << endl; - //kdDebug(TDEIO_SFTP_DB) << "getPacket(): Copy Message size = " << msg.size() << endl; + int method = ssh_auth_list(mSession); + bool firstTime = true; + bool dlgResult; + while (rc != SSH_AUTH_SUCCESS) { - msgLen -= len; + // Try to authenticate with public key first + kdDebug(TDEIO_SFTP_DB) << "Trying to authenticate public key" << endl; + if (method & SSH_AUTH_METHOD_PUBLICKEY) { + rc = ssh_userauth_autopubkey(mSession, NULL); + if (rc == SSH_AUTH_ERROR) { + closeConnection(); + error(TDEIO::ERR_COULD_NOT_LOGIN, i18n("Authentication failed.")); + return; + } else if (rc == SSH_AUTH_SUCCESS) { + break; + } } - b.close(); + info.caption = i18n("SFTP Login"); + info.readOnly = false; + if (firstTime) { + info.prompt = i18n("Please enter your username and password."); + } else { + info.prompt = i18n("Login failed.\nPlease confirm your username and password, and enter them again."); + } + dlgResult = openPassDlg(info); + + // Handle user canceled or dialog failed to open... + if (!dlgResult) { + kdDebug(TDEIO_SFTP_DB) << "User canceled, dlgResult = " << dlgResult << endl; + closeConnection(); + error(TDEIO::ERR_USER_CANCELED, TQString()); + return; + } - return true; -} + firstTime = false; -/** Send an sftp packet to stdin of the ssh process. */ -bool sftpProtocol::putPacket(TQByteArray& p){ -// kdDebug(TDEIO_SFTP_DB) << "putPacket(): size == " << p.size() << endl; - int ret; - ret = atomicio(ssh.stdioFd(), p.data(), p.size(), false /*write*/); - if( ret <= 0 ) { - kdDebug(TDEIO_SFTP_DB) << "putPacket(): write failed, ret =" << ret << - ", error = " << strerror(errno) << endl; - return false; + if (mUsername != info.username) { + kdDebug(TDEIO_SFTP_DB) << "Username changed from " << mUsername + << " to " << info.username << endl; } + mUsername = info.username; + mPassword = info.password; - return true; -} + // Try to authenticate with keyboard interactive + kdDebug(TDEIO_SFTP_DB) << "Trying to authenticate with keyboard interactive" << endl; + if (method & SSH_AUTH_METHOD_INTERACTIVE) { + rc = authenticateKeyboardInteractive(info); + if (rc == SSH_AUTH_ERROR) { + closeConnection(); + error(TDEIO::ERR_COULD_NOT_LOGIN, i18n("Authentication failed.")); + return; + } else if (rc == SSH_AUTH_SUCCESS) { + break; + } + } -/** 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){ + // Try to authenticate with password + kdDebug(TDEIO_SFTP_DB) << "Trying to authenticate with password" << endl; + if (method & SSH_AUTH_METHOD_PASSWORD) { + rc = ssh_userauth_password(mSession, mUsername.utf8().data(), + mPassword.utf8().data()); + if (rc == SSH_AUTH_ERROR) { + closeConnection(); + error(TDEIO::ERR_COULD_NOT_LOGIN, i18n("Authentication failed.")); + return; + } else if (rc == SSH_AUTH_SUCCESS) { + break; + } + } + } - kdDebug(TDEIO_SFTP_DB) << "sftpRealPath(" << url << ", newUrl)" << endl; + // start sftp session + kdDebug(TDEIO_SFTP_DB) << "Trying to request the sftp session" << endl; + mSftp = sftp_new(mSession); + if (mSftp == NULL) { + closeConnection(); + error(TDEIO::ERR_COULD_NOT_LOGIN, i18n("Unable to request the SFTP subsystem. " + "Make sure SFTP is enabled on the server.")); + return; + } - TQCString path = remoteEncoding()->encode(url.path()); - uint len = path.length(); + kdDebug(TDEIO_SFTP_DB) << "Trying to initialize the sftp session" << endl; + if (sftp_init(mSftp) < 0) { + closeConnection(); + error(TDEIO::ERR_COULD_NOT_LOGIN, i18n("Could not initialize the SFTP session.")); + return; + } - TQ_UINT32 id, expectedId; - id = expectedId = mMsgId++; + // Login succeeded! + infoMessage(i18n("Successfully connected to %1").arg(mHost)); + info.url.setProtocol("sftp"); + info.url.setHost(mHost); + info.url.setPort(mPort); + info.url.setUser(mUsername); + info.username = mUsername; + info.password = mPassword; - TQByteArray p; - TQDataStream s(p, IO_WriteOnly); - s << TQ_UINT32(1 /*type*/ + 4 /*id*/ + 4 /*str length*/ + len); - s << (TQ_UINT8)SSH2_FXP_REALPATH; - s << id; - s.writeBytes(path.data(), len); + kdDebug(TDEIO_SFTP_DB) << "Caching info.username = " << info.username + << ", info.url = " << info.url.prettyURL() << endl; - putPacket(p); - getPacket(p); + cacheAuthentication(info); - TQ_UINT8 type; - TQDataStream r(p, IO_ReadOnly); + //setTimeoutSpecialCommand(TDEIO_SFTP_SPECIAL_TIMEOUT); - r >> type >> id; - if( id != expectedId ) { - kdError(TDEIO_SFTP_DB) << "sftpRealPath: sftp packet id mismatch" << endl; - return -1; - } + mConnected = true; + connected(); - if( type == SSH2_FXP_STATUS ) { - TQ_UINT32 code; - r >> code; - return code; - } + mPassword.fill('x'); + mPassword = ""; + info.password.fill('x'); + info.password = ""; - if( type != SSH2_FXP_NAME ) { - kdError(TDEIO_SFTP_DB) << "sftpRealPath(): unexpected packet type of " << type << endl; - return -1; - } + return; +} - TQ_UINT32 count; - r >> count; - if( count != 1 ) { - kdError(TDEIO_SFTP_DB) << "sftpRealPath(): Bad number of file attributes for realpath command" << endl; - return -1; - } +void sftpProtocol::closeConnection() { + kdDebug(TDEIO_SFTP_DB) << "closeConnection()" << endl; - TQCString newPath; - r >> newPath; + sftp_free(mSftp); + mSftp = NULL; - newPath.truncate(newPath.size()); - if (newPath.isEmpty()) - newPath = "/"; - newUrl.setPath(newPath); + ssh_disconnect(mSession); + mSession = NULL; - return SSH2_FX_OK; + mConnected = false; } -sftpProtocol::Status sftpProtocol::doProcessStatus(TQ_UINT8 code, const TQString& message) -{ - Status res; - res.code = 0; - res.size = 0; - res.text = message; - - switch(code) - { - case SSH2_FX_OK: - case SSH2_FX_EOF: - 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").arg(code); - res.code = ERR_UNKNOWN; - } - - return res; +#if 0 +void sftpProtocol::special(const TQByteArray &data) { + int rc; + kdDebug(TDEIO_SFTP_DB) << "special(): polling"; + + /* + * channel_poll() returns the number of bytes that may be read on the + * channel. It does so by checking the input buffer and eventually the + * network socket for data to read. If the input buffer is not empty, it + * will not probe the network (and such not read packets nor reply to + * keepalives). + * + * As channel_poll can act on two specific buffers (a channel has two + * different stream: stdio and stderr), polling for data on the stderr + * stream has more chance of not being in the problematic case (data left + * in the buffer). Checking the return value (for >0) would be a good idea + * to debug the problem. + */ + rc = channel_poll(mSftp->channel, 0); + if (rc > 0) { + rc = channel_poll(mSftp->channel, 1); + } + + if (rc < 0) { + kdDebug(TDEIO_SFTP_DB) << "channel_poll failed: " << ssh_get_error(mSession); + } + + setTimeoutSpecialCommand(TDEIO_SFTP_SPECIAL_TIMEOUT); } +#endif -/** Process SSH_FXP_STATUS packets. */ -void sftpProtocol::processStatus(TQ_UINT8 code, const TQString& message){ - Status st = doProcessStatus( code, message ); - if( st.code != 0 ) - error( st.code, st.text ); -} +void sftpProtocol::statMime(const KURL &url) { + kdDebug(TDEIO_SFTP_DB) << "stat: " << url.url() << endl; -/** Opens a directory handle for url.path. Returns true if succeeds. */ -int sftpProtocol::sftpOpenDirectory(const KURL& url, TQByteArray& handle){ + openConnection(); + if (!mConnected) { + error(TDEIO::ERR_CONNECTION_BROKEN, url.prettyURL()); + return; + } - kdDebug(TDEIO_SFTP_DB) << "sftpOpenDirectory(" << url << ", handle)" << endl; + const TQString path = url.path(); + const TQByteArray path_c = path.utf8(); - TQCString path = remoteEncoding()->encode(url.path()); - uint len = path.length(); + sftp_attributes sb = sftp_lstat(mSftp, path_c.data()); + if (sb == NULL) { + reportError(url, sftp_get_error(mSftp)); + return; + } - TQ_UINT32 id, expectedId; - id = expectedId = mMsgId++; + switch (sb->type) { + case SSH_FILEXFER_TYPE_DIRECTORY: + sftp_attributes_free(sb); + emit mimeType("inode/directory"); + return; + case SSH_FILEXFER_TYPE_SPECIAL: + case SSH_FILEXFER_TYPE_UNKNOWN: + error(TDEIO::ERR_CANNOT_OPEN_FOR_READING, url.prettyURL()); + sftp_attributes_free(sb); + return; + case SSH_FILEXFER_TYPE_SYMLINK: + case SSH_FILEXFER_TYPE_REGULAR: + break; + } - TQByteArray p; - TQDataStream s(p, IO_WriteOnly); - s << (TQ_UINT32)(1 /*type*/ + 4 /*id*/ + 4 /*str length*/ + len); - s << (TQ_UINT8)SSH2_FXP_OPENDIR; - s << (TQ_UINT32)id; - s.writeBytes(path.data(), len); + size_t fileSize = sb->size; + sftp_attributes_free(sb); - putPacket(p); - getPacket(p); + int flags = 0; - TQDataStream r(p, IO_ReadOnly); - TQ_UINT8 type; + flags = O_RDONLY; - r >> type >> id; - if( id != expectedId ) { - kdError(TDEIO_SFTP_DB) << "sftpOpenDirectory: sftp packet id mismatch: " << - "expected " << expectedId << ", got " << id << endl; - return -1; - } + mOpenFile = sftp_open(mSftp, path_c.data(), flags, 0); - if( type == SSH2_FXP_STATUS ) { - TQ_UINT32 errCode; - r >> errCode; - return errCode; - } + if (mOpenFile == NULL) { + error(TDEIO::ERR_CANNOT_OPEN_FOR_READING, path); + return; + } - if( type != SSH2_FXP_HANDLE ) { - kdError(TDEIO_SFTP_DB) << "sftpOpenDirectory: unexpected message type of " << type << endl; - return -1; - } + // Determine the mimetype of the file to be retrieved, and emit it. + // This is mandatory in all slaves (for KRun/BrowserRun to work). + // If we're not opening the file ReadOnly or ReadWrite, don't attempt to + // read the file and send the mimetype. + size_t bytesRequested = 1024; + ssize_t bytesRead = 0; + TQByteArray buffer(bytesRequested); + + bytesRead = sftp_read(mOpenFile, buffer.data(), bytesRequested); + if (bytesRead < 0) { + error(TDEIO::ERR_COULD_NOT_READ, mOpenUrl.prettyURL()); + closeFile(); + return; + } else { + TQByteArray fileData; + fileData.setRawData(buffer.data(), bytesRead); + KMimeMagicResult *p_mimeType = KMimeMagic::self()->findBufferFileType(fileData, mOpenUrl.fileName()); + emit mimeType(p_mimeType->mimeType()); + } - r >> handle; - if( handle.size() > 256 ) { - kdError(TDEIO_SFTP_DB) << "sftpOpenDirectory: handle exceeds max length" << endl; - return -1; - } + sftp_close(mOpenFile); - kdDebug(TDEIO_SFTP_DB) << "sftpOpenDirectory: handle (" << handle.size() << "): [" << handle << "]" << endl; - return SSH2_FX_OK; + mOpenFile = NULL; } -/** Closes a directory or file handle. */ -int sftpProtocol::sftpClose(const TQByteArray& handle){ - - kdDebug(TDEIO_SFTP_DB) << "sftpClose()" << endl; - - TQ_UINT32 id, expectedId; - id = expectedId = mMsgId++; - - TQByteArray p; - TQDataStream s(p, IO_WriteOnly); - s << (TQ_UINT32)(1 /*type*/ + 4 /*id*/ + 4 /*str length*/ + handle.size()); - s << (TQ_UINT8)SSH2_FXP_CLOSE; - s << (TQ_UINT32)id; - s << handle; +#if 0 +void sftpProtocol::read(TDEIO::filesize_t bytes) { + kdDebug(TDEIO_SFTP_DB) << "read, offset = " << openOffset << ", bytes = " << bytes; - putPacket(p); - getPacket(p); + ASSERT(mOpenFile != NULL); - TQDataStream r(p, IO_ReadOnly); - TQ_UINT8 type; + TQVarLengthArray buffer(bytes); - r >> type >> id; - if( id != expectedId ) { - kdError(TDEIO_SFTP_DB) << "sftpClose: sftp packet id mismatch" << endl; - return -1; - } - - if( type != SSH2_FXP_STATUS ) { - kdError(TDEIO_SFTP_DB) << "sftpClose: unexpected message type of " << type << endl; - return -1; - } + ssize_t bytesRead = sftp_read(mOpenFile, buffer.data(), bytes); + ASSERT(bytesRead <= static_cast(bytes)); - TQ_UINT32 code; - r >> code; - if( code != SSH2_FX_OK ) { - kdError(TDEIO_SFTP_DB) << "sftpClose: close failed with err code " << code << endl; - } + if (bytesRead < 0) { + kdDebug(TDEIO_SFTP_DB) << "Could not read " << mOpenUrl; + error(TDEIO::ERR_COULD_NOT_READ, mOpenUrl.prettyURL()); + close(); + return; + } - return code; + TQByteArray fileData = TQByteArray::fromRawData(buffer.data(), bytesRead); + data(fileData); } -/** Set a files attributes. */ -int sftpProtocol::sftpSetStat(const KURL& url, const sftpFileAttr& attr){ +void sftpProtocol::write(const TQByteArray &data) { + kdDebug(TDEIO_SFTP_DB) << "write, offset = " << openOffset << ", bytes = " << data.size(); - kdDebug(TDEIO_SFTP_DB) << "sftpSetStat(" << url << ", attr)" << endl; + ASSERT(mOpenFile != NULL); - TQCString path = remoteEncoding()->encode(url.path()); - uint len = path.length(); - - TQ_UINT32 id, expectedId; - id = expectedId = mMsgId++; + ssize_t bytesWritten = sftp_write(mOpenFile, data.data(), data.size()); + if (bytesWritten < 0) { + kdDebug(TDEIO_SFTP_DB) << "Could not write to " << mOpenUrl; + error(TDEIO::ERR_COULD_NOT_WRITE, mOpenUrl.prettyURL()); + close(); + return; + } - TQByteArray p; - TQDataStream s(p, IO_WriteOnly); - s << (TQ_UINT32)(1 /*type*/ + 4 /*id*/ + 4 /*str length*/ + len + attr.size()); - s << (TQ_UINT8)SSH2_FXP_SETSTAT; - s << (TQ_UINT32)id; - s.writeBytes(path.data(), len); - s << attr; + written(bytesWritten); +} - putPacket(p); - getPacket(p); +void sftpProtocol::seek(TDEIO::filesize_t offset) { + kdDebug(TDEIO_SFTP_DB) << "seek, offset = " << offset; - TQDataStream r(p, IO_ReadOnly); - TQ_UINT8 type; + ASSERT(mOpenFile != NULL); - r >> type >> id; - if( id != expectedId ) { - kdError(TDEIO_SFTP_DB) << "sftpSetStat(): sftp packet id mismatch" << endl; - return -1; - // XXX How do we do a fatal error? - } + if (sftp_seek64(mOpenFile, static_cast(offset)) < 0) { + error(TDEIO::ERR_COULD_NOT_SEEK, mOpenUrl.path()); + close(); + } - if( type != SSH2_FXP_STATUS ) { - kdError(TDEIO_SFTP_DB) << "sftpSetStat(): unexpected message type of " << type << endl; - return -1; - } + position(sftp_tell64(mOpenFile)); +} +#endif - TQ_UINT32 code; - r >> code; - if( code != SSH2_FX_OK ) { - kdError(TDEIO_SFTP_DB) << "sftpSetStat(): set stat failed with err code " << code << endl; - } +void sftpProtocol::closeFile() { + if (mOpenFile) { + sftp_close(mOpenFile); - return code; + mOpenFile = NULL; + finished(); + } } -/** Sends a sftp command to remove a file or directory. */ -int sftpProtocol::sftpRemove(const KURL& url, bool isfile){ +void sftpProtocol::get(const KURL& url) { + kdDebug(TDEIO_SFTP_DB) << "get(): " << url.url() << endl; - kdDebug(TDEIO_SFTP_DB) << "sftpRemove(): " << url << ", isFile ? " << isfile << endl; + openConnection(); + if (!mConnected) { + return; + } - TQCString path = remoteEncoding()->encode(url.path()); - uint len = path.length(); + TQByteArray path = url.path().utf8(); - TQ_UINT32 id, expectedId; - id = expectedId = mMsgId++; + char buf[MAX_XFER_BUF_SIZE] = {0}; + sftp_file file = NULL; + ssize_t bytesread = 0; + // time_t curtime = 0; + time_t lasttime = 0; + time_t starttime = 0; + ssize_t totalbytesread = 0; - TQByteArray p; - TQDataStream s(p, IO_WriteOnly); - s << (TQ_UINT32)(1 /*type*/ + 4 /*id*/ + 4 /*str length*/ + len); - s << (TQ_UINT8)(isfile ? SSH2_FXP_REMOVE : SSH2_FXP_RMDIR); - s << (TQ_UINT32)id; - s.writeBytes(path.data(), len); + sftp_attributes sb = sftp_lstat(mSftp, path.data()); + if (sb == NULL) { + reportError(url, sftp_get_error(mSftp)); + return; + } - putPacket(p); - getPacket(p); + switch (sb->type) { + case SSH_FILEXFER_TYPE_DIRECTORY: + error(TDEIO::ERR_IS_DIRECTORY, url.prettyURL()); + sftp_attributes_free(sb); + return; + case SSH_FILEXFER_TYPE_SPECIAL: + case SSH_FILEXFER_TYPE_UNKNOWN: + error(TDEIO::ERR_CANNOT_OPEN_FOR_READING, url.prettyURL()); + sftp_attributes_free(sb); + return; + case SSH_FILEXFER_TYPE_SYMLINK: + case SSH_FILEXFER_TYPE_REGULAR: + break; + } - TQDataStream r(p, IO_ReadOnly); - TQ_UINT8 type; + // Open file + file = sftp_open(mSftp, path.data(), O_RDONLY, 0); + if (file == NULL) { + error( TDEIO::ERR_CANNOT_OPEN_FOR_READING, url.prettyURL()); + sftp_attributes_free(sb); + return; + } - r >> type >> id; - if( id != expectedId ) { - kdError(TDEIO_SFTP_DB) << "del(): sftp packet id mismatch" << endl; - return -1; + // Determine the mimetype of the file to be retrieved, and emit it. + // This is mandatory in all slaves (for KRun/BrowserRun to work) + // In real "remote" slaves, this is usually done using findByNameAndContent + // after receiving some data. But we don't know how much data the mimemagic rules + // need, so for local files, better use findByUrl with localUrl=true. + KMimeType::Ptr mt = KMimeType::findByURL( url, sb->permissions, false /* remote URL */ ); + emit mimeType( mt->name() ); // FIXME test me + + kdDebug(TDEIO_SFTP_DB) << "Total size: " << TQString::number(sb->size) << endl; + // Set the total size + totalSize(sb->size); + + const TQString resumeOffset = metaData(TQString("resume")); + if (!resumeOffset.isEmpty()) { + bool ok; + ssize_t offset = resumeOffset.toLong(&ok); + if (ok && (offset > 0) && ((unsigned long long) offset < sb->size)) + { + if (sftp_seek64(file, offset) == 0) { + canResume(); + totalbytesread = offset; + kdDebug(TDEIO_SFTP_DB) << "Resume offset: " << TQString::number(offset) << endl; + } } + } - if( type != SSH2_FXP_STATUS ) { - kdError(TDEIO_SFTP_DB) << "del(): unexpected message type of " << type << endl; - return -1; - } + if (file != NULL) { + bool isFirstPacket = true; + lasttime = starttime = time(NULL); + + for (;;) { + bytesread = sftp_read(file, buf, MAX_XFER_BUF_SIZE); + kdDebug(TDEIO_SFTP_DB) << "bytesread=" << TQString::number(bytesread) << endl; + if (bytesread == 0) { + // All done reading + break; + } else if (bytesread < 0) { + kdDebug(TDEIO_SFTP_DB) << "Failed to read"; + error(TDEIO::ERR_COULD_NOT_READ, url.prettyURL()); + sftp_attributes_free(sb); + return; + } - TQ_UINT32 code; - r >> code; - if( code != SSH2_FX_OK ) { - kdError(TDEIO_SFTP_DB) << "del(): del failed with err code " << code << endl; - } + TQByteArray filedata; + filedata.setRawData(buf, bytesread); + if (isFirstPacket) { + KMimeMagicResult *p_mimeType = KMimeMagic::self()->findBufferFileType(filedata, mOpenUrl.fileName()); + mimeType(p_mimeType->mimeType()); + kdDebug(TDEIO_SFTP_DB) << "mimetype=" << p_mimeType->mimeType() << endl; + isFirstPacket = false; + } + data(filedata); + filedata.resetRawData(buf, bytesread); - return code; -} + // increment total bytes read + totalbytesread += bytesread; -/** Send a sftp command to rename a file or directoy. */ -int sftpProtocol::sftpRename(const KURL& src, const KURL& dest){ + processedSize(totalbytesread); + } - kdDebug(TDEIO_SFTP_DB) << "sftpRename(" << src << " -> " << dest << ")" << endl; + kdDebug(TDEIO_SFTP_DB) << "size processed=" << totalbytesread << endl; + sftp_close(file); + //data(TQByteArray()); + processedSize((sb->size)); + } - TQCString srcPath = remoteEncoding()->encode(src.path()); - TQCString destPath = remoteEncoding()->encode(dest.path()); + sftp_attributes_free(sb); + finished(); +} - uint slen = srcPath.length(); - uint dlen = destPath.length(); +void sftpProtocol::put(const KURL& url, int permissions, bool overwrite, bool resume) { + kdDebug(TDEIO_SFTP_DB) << "put(): " << url.url() + << " , permissions = " << TQString::number(permissions) + << ", overwrite = " << overwrite + << ", resume = " << resume << endl; - TQ_UINT32 id, expectedId; - id = expectedId = mMsgId++; + openConnection(); + if (!mConnected) { + return; + } - TQByteArray p; - TQDataStream s(p, IO_WriteOnly); - s << (TQ_UINT32)(1 /*type*/ + 4 /*id*/ + - 4 /*str length*/ + slen + - 4 /*str length*/ + dlen); - s << (TQ_UINT8)SSH2_FXP_RENAME; - s << (TQ_UINT32)id; - s.writeBytes(srcPath.data(), slen); - s.writeBytes(destPath.data(), dlen); + const TQString dest_orig = url.path(); + const TQByteArray dest_orig_c = dest_orig.utf8(); + const TQString dest_part = dest_orig + ".part"; + const TQByteArray dest_part_c = dest_part.utf8(); + uid_t owner = 0; + gid_t group = 0; + + sftp_attributes sb = sftp_lstat(mSftp, dest_orig_c.data()); + const bool bOrigExists = (sb != NULL); + bool bPartExists = false; + const bool bMarkPartial = config()->readEntry("MarkPartial", "true") == "true"; + + // Don't change permissions of the original file + if (bOrigExists) { + permissions = sb->permissions; + owner = sb->uid; + group = sb->gid; + } - putPacket(p); - getPacket(p); + if (bMarkPartial) { + sftp_attributes sbPart = sftp_lstat(mSftp, dest_part_c.data()); + bPartExists = (sbPart != NULL); - TQDataStream r(p, IO_ReadOnly); - TQ_UINT8 type; + if (bPartExists && !resume && !overwrite && + sbPart->size > 0 && sbPart->type == SSH_FILEXFER_TYPE_REGULAR) { + kdDebug(TDEIO_SFTP_DB) << "put : calling canResume with " + << TQString::number(sbPart->size) << endl; - r >> type >> id; - if( id != expectedId ) { - kdError(TDEIO_SFTP_DB) << "sftpRename(): sftp packet id mismatch" << endl; - return -1; - } + // Maybe we can use this partial file for resuming + // Tell about the size we have, and the app will tell us + // if it's ok to resume or not. + if (canResume(sbPart->size)) { + resume = true; + } - if( type != SSH2_FXP_STATUS ) { - kdError(TDEIO_SFTP_DB) << "sftpRename(): unexpected message type of " << type << endl; - return -1; - } + kdDebug(TDEIO_SFTP_DB) << "put got answer " << resume << endl; - int code; - r >> code; - if( code != SSH2_FX_OK ) { - kdError(TDEIO_SFTP_DB) << "sftpRename(): rename failed with err code " << code << endl; + delete sbPart; } + } - return code; -} -/** Get directory listings. */ -int sftpProtocol::sftpReadDir(const TQByteArray& handle, const KURL& url){ - // url is needed so we can lookup the link destination - kdDebug(TDEIO_SFTP_DB) << "sftpReadDir(): " << url << endl; - - TQ_UINT32 id, expectedId, count; - TQ_UINT8 type; - - sftpFileAttr attr (remoteEncoding()); - attr.setDirAttrsFlag(true); - - TQByteArray p; - TQDataStream s(p, IO_WriteOnly); - id = expectedId = mMsgId++; - s << (TQ_UINT32)(1 /*type*/ + 4 /*id*/ + 4 /*str length*/ + handle.size()); - s << (TQ_UINT8)SSH2_FXP_READDIR; - s << (TQ_UINT32)id; - s << handle; - - putPacket(p); - getPacket(p); - - TQDataStream r(p, IO_ReadOnly); - r >> type >> id; - - if( id != expectedId ) { - kdError(TDEIO_SFTP_DB) << "sftpReadDir(): sftp packet id mismatch" << endl; - return -1; + if (bOrigExists && !(overwrite) && !(resume)) { + if (sb->type == SSH_FILEXFER_TYPE_DIRECTORY) { + error(TDEIO::ERR_DIR_ALREADY_EXIST, dest_orig); + } else { + error(TDEIO::ERR_FILE_ALREADY_EXIST, dest_orig); } + sftp_attributes_free(sb); + return; + } - int code; - if( type == SSH2_FXP_STATUS ) { - r >> code; - return code; - } + int result; + TQByteArray dest; + sftp_file file = NULL; + + // Loop until we got 0 (end of data) + do { + TQByteArray buffer; + dataReq(); // Request for data + result = readData(buffer); + + if (result >= 0) { + if (dest.isEmpty()) { + if (bMarkPartial) { + kdDebug(TDEIO_SFTP_DB) << "Appending .part extension to " << dest_orig << endl; + dest = dest_part_c; + if (bPartExists && !(resume)) { + kdDebug(TDEIO_SFTP_DB) << "Deleting partial file " << dest_part << endl; + sftp_unlink(mSftp, dest_part_c.data()); + // Catch errors when we try to open the file. + } + } else { + dest = dest_orig_c; + if (bOrigExists && !(resume)) { + kdDebug(TDEIO_SFTP_DB) << "Deleting destination file " << dest_orig << endl; + sftp_unlink(mSftp, dest_orig_c.data()); + // Catch errors when we try to open the file. + } + } // bMarkPartial + + if ((resume)) { + sftp_attributes fstat; + + kdDebug(TDEIO_SFTP_DB) << "Trying to append: " << dest.data() << endl; + file = sftp_open(mSftp, dest.data(), O_RDWR, 0); // append if resuming + if (file) { + fstat = sftp_fstat(file); + if (fstat) { + sftp_seek64(file, fstat->size); // Seek to end TODO + sftp_attributes_free(fstat); + } + } + } else { + mode_t initialMode; + + if (permissions != -1) { + initialMode = permissions | S_IWUSR | S_IRUSR; + } else { + initialMode = 0644; + } + + kdDebug(TDEIO_SFTP_DB) << "Trying to open: " << dest.data() << ", mode=" << TQString::number(initialMode) << endl; + file = sftp_open(mSftp, dest.data(), O_CREAT | O_TRUNC | O_WRONLY, initialMode); + } // resume + + if (file == NULL) { + kdDebug(TDEIO_SFTP_DB) << "COULD NOT WRITE " << dest.data() + << " permissions=" << permissions + << " error=" << ssh_get_error(mSession) << endl; + if (sftp_get_error(mSftp) == SSH_FX_PERMISSION_DENIED) { + error(TDEIO::ERR_WRITE_ACCESS_DENIED, TQString::fromUtf8(dest)); + } else { + error(TDEIO::ERR_CANNOT_OPEN_FOR_WRITING, TQString::fromUtf8(dest)); + } + sftp_attributes_free(sb); + finished(); + return; + } // file + } // dest.isEmpty - if( type != SSH2_FXP_NAME ) { - kdError(TDEIO_SFTP_DB) << "tdeio_sftpProtocl::sftpReadDir(): Unexpected message" << endl; - return -1; + ssize_t bytesWritten = sftp_write(file, buffer.data(), buffer.size()); + if (bytesWritten < 0) { + error(TDEIO::ERR_COULD_NOT_WRITE, dest_orig); + result = -1; + } + } // result + } while (result > 0); + sftp_attributes_free(sb); + + // An error occurred deal with it. + if (result < 0) { + kdDebug(TDEIO_SFTP_DB) << "Error during 'put'. Aborting." << endl; + + if (file != NULL) { + sftp_close(file); + + sftp_attributes attr = sftp_stat(mSftp, dest.data()); + if (bMarkPartial && attr != NULL) { + size_t size = config()->readEntry("MinimumKeepSize", DEFAULT_MINIMUM_KEEP_SIZE).toLong(); + if (attr->size < size) { + sftp_unlink(mSftp, dest.data()); + } + } + delete attr; + sftp_attributes_free(attr); } - r >> count; - kdDebug(TDEIO_SFTP_DB) << "sftpReadDir(): got " << count << " entries" << endl; + //::exit(255); + finished(); + return; + } - while(count--) { - r >> attr; + if (file == NULL) { // we got nothing to write out, so we never opened the file + finished(); + return; + } - if( S_ISLNK(attr.permissions()) ) { - KURL myurl ( url ); - myurl.addPath(attr.filename()); + if (sftp_close(file) < 0) { + kdWarning(TDEIO_SFTP_DB) << "Error when closing file descriptor" << endl; + error(TDEIO::ERR_COULD_NOT_WRITE, dest_orig); + return; + } - // Stat the symlink to find out its type... - sftpFileAttr attr2 (remoteEncoding()); - (void) sftpStat(myurl, attr2); + // after full download rename the file back to original name + if (bMarkPartial) { + // If the original URL is a symlink and we were asked to overwrite it, + // remove the symlink first. This ensures that we do not overwrite the + // current source if the symlink points to it. + if ((overwrite)) { + sftp_unlink(mSftp, dest_orig_c.data()); + } - attr.setLinkType(attr2.linkType()); - attr.setLinkDestination(attr2.linkDestination()); - } + if (sftp_rename(mSftp, dest.data(), dest_orig_c.data()) < 0) { + kdWarning(TDEIO_SFTP_DB) << " Couldn't rename " << dest.data() << " to " << dest_orig << endl; + error(TDEIO::ERR_CANNOT_RENAME_PARTIAL, dest_orig); + return; + } + } - listEntry(attr.entry(), false); + // set final permissions + if (permissions != -1 && !(resume)) { + kdDebug(TDEIO_SFTP_DB) << "Trying to set final permissions of " << dest_orig << " to " << TQString::number(permissions) << endl; + if (sftp_chmod(mSftp, dest_orig_c.data(), permissions) < 0) { + warning(i18n( "Could not change permissions for\n%1").arg(dest_orig)); } + } - listEntry(attr.entry(), true); + // set original owner and group + if (bOrigExists) { + kdDebug(TDEIO_SFTP_DB) << "Trying to restore original owner and group of " << dest_orig << endl; + if (sftp_chown(mSftp, dest_orig_c.data(), owner, group) < 0) { + // warning(i18n( "Could not change owner and group for\n%1", dest_orig)); + } + } - return SSH2_FX_OK; + // set modification time +#if 0 + const TQString mtimeStr = metaData("modified"); + if (!mtimeStr.isEmpty()) { + TQDateTime dt = TQDateTime::fromString(mtimeStr, TQt::ISODate); + if (dt.isValid()) { + struct timeval times[2]; + + sftp_attributes attr = sftp_lstat(mSftp, dest_orig_c.data()); + if (attr != NULL) { + times[0].tv_sec = attr->atime; //// access time, unchanged + times[1].tv_sec = dt.toTime_t(); // modification time + times[0].tv_usec = times[1].tv_usec = 0; + + sftp_utimes(mSftp, dest_orig_c.data(), times); + sftp_attributes_free(attr); + } + } + } +#endif + // We have done our job => finish + finished(); } -int sftpProtocol::sftpReadLink(const KURL& url, TQString& target){ - - kdDebug(TDEIO_SFTP_DB) << "sftpReadLink(): " << url << endl; - - TQCString path = remoteEncoding()->encode(url.path()); - uint len = path.length(); - - //kdDebug(TDEIO_SFTP_DB) << "sftpReadLink(): Encoded Path: " << path << endl; - //kdDebug(TDEIO_SFTP_DB) << "sftpReadLink(): Encoded Size: " << len << endl; - - TQ_UINT32 id, expectedId; - id = expectedId = mMsgId++; +void sftpProtocol::copy(const KURL &src, const KURL &dest, int permissions, bool overwrite) +{ + kdDebug(TDEIO_SFTP_DB) << src.url() << " -> " << dest.url() << " , permissions = " << TQString::number(permissions) + << ", overwrite = " << overwrite << endl; - TQByteArray p; - TQDataStream s(p, IO_WriteOnly); - s << (TQ_UINT32)(1 /*type*/ + 4 /*id*/ + 4 /*str length*/ + len); - s << (TQ_UINT8)SSH2_FXP_READLINK; - s << id; - s.writeBytes(path.data(), len); + error(TDEIO::ERR_UNSUPPORTED_ACTION, TQString()); +} +void sftpProtocol::stat(const KURL& url) { + kdDebug(TDEIO_SFTP_DB) << url.url() << endl; - putPacket(p); - getPacket(p); + openConnection(); + if (!mConnected) { + return; + } - TQ_UINT8 type; - TQDataStream r(p, IO_ReadOnly); + if (! url.hasPath() || TQDir::isRelativePath(url.path()) || + url.path().contains("/./") || url.path().contains("/../")) { + TQString cPath; - r >> type >> id; - if( id != expectedId ) { - kdError(TDEIO_SFTP_DB) << "sftpReadLink(): sftp packet id mismatch" << endl; - return -1; + if (url.hasPath()) { + cPath = canonicalizePath(url.path()); + } else { + cPath = canonicalizePath(TQString(".")); } - if( type == SSH2_FXP_STATUS ) { - TQ_UINT32 code; - r >> code; - kdDebug(TDEIO_SFTP_DB) << "sftpReadLink(): read link failed with code " << code << endl; - return code; + if (cPath.isEmpty()) { + error(TDEIO::ERR_MALFORMED_URL, url.prettyURL()); + return; } + KURL redir(url); + redir.setPath(cPath); + redirection(redir); - if( type != SSH2_FXP_NAME ) { - kdError(TDEIO_SFTP_DB) << "sftpReadLink(): unexpected packet type of " << type << endl; - return -1; - } + kdDebug(TDEIO_SFTP_DB) << "redirecting to " << redir.url() << endl; - TQ_UINT32 count; - r >> count; - if( count != 1 ) { - kdError(TDEIO_SFTP_DB) << "sftpReadLink(): Bad number of file attributes for realpath command" << endl; - return -1; - } + finished(); + return; + } - TQCString linkAddress; - r >> linkAddress; + TQByteArray path = url.path().utf8(); - linkAddress.truncate(linkAddress.size()); - kdDebug(TDEIO_SFTP_DB) << "sftpReadLink(): Link address: " << linkAddress << endl; + const TQString sDetails = metaData(TQString("details")); + const int details = sDetails.isEmpty() ? 2 : sDetails.toInt(); - target = remoteEncoding()->decode(linkAddress); + UDSEntry entry; + entry.clear(); + if (!createUDSEntry(url.fileName(), path, entry, details)) { + error(TDEIO::ERR_DOES_NOT_EXIST, url.prettyURL()); + return; + } - return SSH2_FX_OK; -} + statEntry(entry); -int sftpProtocol::sftpSymLink(const TQString& _target, const KURL& dest){ + finished(); +} - TQCString destPath = remoteEncoding()->encode(dest.path()); - TQCString target = remoteEncoding()->encode(_target); - uint dlen = destPath.length(); - uint tlen = target.length(); +void sftpProtocol::mimetype(const KURL& url){ + kdDebug(TDEIO_SFTP_DB) << url.url() << endl; - kdDebug(TDEIO_SFTP_DB) << "sftpSymLink(" << target << " -> " << destPath << ")" << endl; + openConnection(); + if (!mConnected) { + return; + } - TQ_UINT32 id, expectedId; - id = expectedId = mMsgId++; + // stat() feeds the mimetype + statMime(url); + closeFile(); - TQByteArray p; - TQDataStream s(p, IO_WriteOnly); - s << (TQ_UINT32)(1 /*type*/ + 4 /*id*/ + - 4 /*str length*/ + tlen + - 4 /*str length*/ + dlen); - s << (TQ_UINT8)SSH2_FXP_SYMLINK; - s << (TQ_UINT32)id; - s.writeBytes(target.data(), tlen); - s.writeBytes(destPath.data(), dlen); + finished(); +} - putPacket(p); - getPacket(p); +void sftpProtocol::listDir(const KURL& url) { + kdDebug(TDEIO_SFTP_DB) << "list directory: " << url.url() << endl; - TQDataStream r(p, IO_ReadOnly); - TQ_UINT8 type; + openConnection(); + if (!mConnected) { + return; + } - r >> type >> id; - if( id != expectedId ) { - kdError(TDEIO_SFTP_DB) << "sftpSymLink(): sftp packet id mismatch" << endl; - return -1; - } + if (! url.hasPath() || TQDir::isRelativePath(url.path()) || + url.path().contains("/./") || url.path().contains("/../")) { + TQString cPath; - if( type != SSH2_FXP_STATUS ) { - kdError(TDEIO_SFTP_DB) << "sftpSymLink(): unexpected message type of " << type << endl; - return -1; + if (url.hasPath()) { + cPath = canonicalizePath(url.path()); + } else { + cPath = canonicalizePath(TQString(".")); } - TQ_UINT32 code; - r >> code; - if( code != SSH2_FX_OK ) { - kdError(TDEIO_SFTP_DB) << "sftpSymLink(): rename failed with err code " << code << endl; + if (cPath.isEmpty()) { + error(TDEIO::ERR_MALFORMED_URL, url.prettyURL()); + return; } + KURL redir(url); + redir.setPath(cPath); + redirection(redir); - return code; -} - -/** Stats a file. */ -int sftpProtocol::sftpStat(const KURL& url, sftpFileAttr& attr) { + kdDebug(TDEIO_SFTP_DB) << "redirecting to " << redir.url() << endl; - kdDebug(TDEIO_SFTP_DB) << "sftpStat(): " << url << endl; - - TQCString path = remoteEncoding()->encode(url.path()); - uint len = path.length(); + finished(); + return; + } - TQ_UINT32 id, expectedId; - id = expectedId = mMsgId++; + TQByteArray path = url.path().utf8(); - TQByteArray p; - TQDataStream s(p, IO_WriteOnly); - s << (TQ_UINT32)(1 /*type*/ + 4 /*id*/ + 4 /*str length*/ + len); - s << (TQ_UINT8)SSH2_FXP_LSTAT; - s << (TQ_UINT32)id; - s.writeBytes(path.data(), len); + sftp_dir dp = sftp_opendir(mSftp, path.data()); + if (dp == NULL) { + reportError(url, sftp_get_error(mSftp)); + return; + } - putPacket(p); - getPacket(p); + sftp_attributes dirent = NULL; + const TQString sDetails = metaData(TQString("details")); + const int details = sDetails.isEmpty() ? 2 : sDetails.toInt(); + TQList entryNames; + UDSEntry entry; - TQDataStream r(p, IO_ReadOnly); - TQ_UINT8 type; + kdDebug(TDEIO_SFTP_DB) << "readdir: " << path.data() << ", details: " << TQString::number(details) << endl; - r >> type >> id; - if( id != expectedId ) { - kdError(TDEIO_SFTP_DB) << "sftpStat(): sftp packet id mismatch" << endl; - return -1; - } + UDSAtom atom; - if( type == SSH2_FXP_STATUS ) { - TQ_UINT32 errCode; - r >> errCode; - kdError(TDEIO_SFTP_DB) << "sftpStat(): stat failed with code " << errCode << endl; - return errCode; - } + for (;;) { + mode_t access; + mode_t type; + char *link; - if( type != SSH2_FXP_ATTRS ) { - kdError(TDEIO_SFTP_DB) << "sftpStat(): unexpected message type of " << type << endl; - return -1; + dirent = sftp_readdir(mSftp, dp); + if (dirent == NULL) { + break; } - r >> attr; - attr.setFilename(url.fileName()); - kdDebug(TDEIO_SFTP_DB) << "sftpStat(): " << attr << endl; + entry.clear(); + atom.m_uds = UDS_NAME; + atom.m_str = TQFile::decodeName(dirent->name); + entry.append(atom); - // 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) ) { + if (dirent->type == SSH_FILEXFER_TYPE_SYMLINK) { + TQCString file = (TQString(path) + "/" + TQFile::decodeName(dirent->name)).utf8().data(); - TQString target; - int code = sftpReadLink( url, target ); + atom.m_uds = UDS_FILE_TYPE; + atom.m_long = S_IFREG; + entry.append(atom); - if ( code != SSH2_FX_OK ) { - kdError(TDEIO_SFTP_DB) << "sftpStat(): Unable to stat symlink destination" << endl; - return -1; + link = sftp_readlink(mSftp, file.data()); + if (link == NULL) { + sftp_attributes_free(dirent); + error(TDEIO::ERR_INTERNAL, i18n("Could not read link: %1").arg(TQString::fromUtf8(file))); + return; + } + atom.m_uds = UDS_LINK_DEST; + atom.m_str = TQFile::decodeName(link); + entry.append(atom); + delete link; + // A symlink -> follow it only if details > 1 + if (details > 1) { + sftp_attributes sb = sftp_stat(mSftp, file.data()); + if (sb == NULL) { + // It is a link pointing to nowhere + type = S_IFMT - 1; + access = S_IRWXU | S_IRWXG | S_IRWXO; + atom.m_uds = UDS_FILE_TYPE; + atom.m_long = type; + entry.append(atom); + atom.m_uds = UDS_ACCESS; + atom.m_long = access; + entry.append(atom); + atom.m_uds = UDS_SIZE; + atom.m_long = 0; + entry.append(atom); + + goto notype; } + sftp_attributes_free(dirent); + dirent = sb; + } + } - kdDebug(TDEIO_SFTP_DB) << "sftpStat(): Resource is a symlink to -> " << target << endl; + switch (dirent->type) { + case SSH_FILEXFER_TYPE_REGULAR: + atom.m_uds = UDS_FILE_TYPE; + atom.m_long = S_IFREG; + entry.append(atom); + break; + case SSH_FILEXFER_TYPE_DIRECTORY: + atom.m_uds = UDS_FILE_TYPE; + atom.m_long = S_IFDIR; + entry.append(atom); + break; + case SSH_FILEXFER_TYPE_SYMLINK: + atom.m_uds = UDS_FILE_TYPE; + atom.m_long = S_IFLNK; + entry.append(atom); + break; + case SSH_FILEXFER_TYPE_SPECIAL: + case SSH_FILEXFER_TYPE_UNKNOWN: + break; + } + + access = dirent->permissions & 07777; + atom.m_uds = UDS_ACCESS; + atom.m_long = access; + entry.append(atom); + + atom.m_uds = UDS_SIZE; + atom.m_long = dirent->size; + entry.append(atom); + +notype: + if (details > 0) { + atom.m_uds = UDS_USER; + if (dirent->owner) { + atom.m_str = TQString::fromUtf8(dirent->owner); + } else { + atom.m_str = TQString::number(dirent->uid); + } + entry.append(atom); - KURL dest( url ); - if( target[0] == '/' ) - dest.setPath(target); - else - dest.setFileName(target); + atom.m_uds = UDS_GROUP; + if (dirent->group) { + atom.m_str = TQString::fromUtf8(dirent->group); + } else { + atom.m_str = TQString::number(dirent->gid); + } + entry.append(atom); - dest.cleanPath(); + atom.m_uds = UDS_ACCESS_TIME; + atom.m_long = dirent->atime; + entry.append(atom); - // Ignore symlinks that point to themselves... - if ( dest != url ) { + atom.m_uds = UDS_MODIFICATION_TIME; + atom.m_long = dirent->mtime; + entry.append(atom); - sftpFileAttr attr2 (remoteEncoding()); - (void) sftpStat(dest, attr2); + atom.m_uds = UDS_MODIFICATION_TIME; + atom.m_long = dirent->createtime; + entry.append(atom); + } - if (attr2.linkType() == 0) - attr.setLinkType(attr2.fileType()); - else - attr.setLinkType(attr2.linkType()); + sftp_attributes_free(dirent); + listEntry(entry, false); + } // for ever + sftp_closedir(dp); + listEntry(entry, true); // ready - attr.setLinkDestination(target); + finished(); +} - kdDebug(TDEIO_SFTP_DB) << "sftpStat(): File type: " << attr.fileType() << endl; - } - } +void sftpProtocol::mkdir(const KURL &url, int permissions) { + kdDebug(TDEIO_SFTP_DB) << "create directory: " << url.url() << endl; - return SSH2_FX_OK; -} + openConnection(); + if (!mConnected) { + return; + } + if (url.path().isEmpty()) { + error(TDEIO::ERR_MALFORMED_URL, url.prettyURL()); + return; + } + const TQString path = url.path(); + const TQByteArray path_c = path.utf8(); -int sftpProtocol::sftpOpen(const KURL& url, const TQ_UINT32 pflags, - const sftpFileAttr& attr, TQByteArray& handle) { - kdDebug(TDEIO_SFTP_DB) << "sftpOpen(" << url << ", handle" << endl; + // Remove existing file or symlink, if requested. + if (metaData(TQString("overwrite")) == TQString("true")) { + kdDebug(TDEIO_SFTP_DB) << "overwrite set, remove existing file or symlink: " << url.url() << endl; + sftp_unlink(mSftp, path_c.data()); + } - TQCString path = remoteEncoding()->encode(url.path()); - uint len = path.length(); + kdDebug(TDEIO_SFTP_DB) << "Trying to create directory: " << path << endl; + sftp_attributes sb = sftp_lstat(mSftp, path_c.data()); + if (sb == NULL) { + if (sftp_mkdir(mSftp, path_c.data(), 0777) < 0) { + reportError(url, sftp_get_error(mSftp)); + sftp_attributes_free(sb); + return; + } else { + kdDebug(TDEIO_SFTP_DB) << "Successfully created directory: " << url.url() << endl; + if (permissions != -1) { + chmod(url, permissions); + } else { + finished(); + } + sftp_attributes_free(sb); + return; + } + } - TQ_UINT32 id, expectedId; - id = expectedId = mMsgId++; + if (sb->type == SSH_FILEXFER_TYPE_DIRECTORY) { + error(TDEIO::ERR_DIR_ALREADY_EXIST, path); + } else { + error(TDEIO::ERR_FILE_ALREADY_EXIST, path); + } - TQByteArray p; - TQDataStream s(p, IO_WriteOnly); - s << (TQ_UINT32)(1 /*type*/ + 4 /*id*/ + - 4 /*str length*/ + len + - 4 /*pflags*/ + attr.size()); - s << (TQ_UINT8)SSH2_FXP_OPEN; - s << (TQ_UINT32)id; - s.writeBytes(path.data(), len); - s << pflags; - s << attr; + sftp_attributes_free(sb); + return; +} - putPacket(p); - getPacket(p); +void sftpProtocol::rename(const KURL& src, const KURL& dest, bool overwrite) { + kdDebug(TDEIO_SFTP_DB) << "rename " << src.url() << " to " << dest.url() << endl; - TQDataStream r(p, IO_ReadOnly); - TQ_UINT8 type; + openConnection(); + if (!mConnected) { + return; + } - r >> type >> id; - if( id != expectedId ) { - kdError(TDEIO_SFTP_DB) << "sftpOpen(): sftp packet id mismatch" << endl; - return -1; - } + TQByteArray qsrc = src.path().utf8(); + TQByteArray qdest = dest.path().utf8(); - if( type == SSH2_FXP_STATUS ) { - TQ_UINT32 errCode; - r >> errCode; - return errCode; + sftp_attributes sb = sftp_lstat(mSftp, qdest.data()); + if (sb != NULL) { + if (!overwrite) { + if (sb->type == SSH_FILEXFER_TYPE_DIRECTORY) { + error(TDEIO::ERR_DIR_ALREADY_EXIST, dest.url()); + } else { + error(TDEIO::ERR_FILE_ALREADY_EXIST, dest.url()); + } + sftp_attributes_free(sb); + return; } - if( type != SSH2_FXP_HANDLE ) { - kdError(TDEIO_SFTP_DB) << "sftpOpen(): unexpected message type of " << type << endl; - return -1; - } + del(dest, sb->type == SSH_FILEXFER_TYPE_DIRECTORY ? true : false); + } + sftp_attributes_free(sb); - r >> handle; - if( handle.size() > 256 ) { - kdError(TDEIO_SFTP_DB) << "sftpOpen(): handle exceeds max length" << endl; - return -1; - } + if (sftp_rename(mSftp, qsrc.data(), qdest.data()) < 0) { + reportError(dest, sftp_get_error(mSftp)); + return; + } - kdDebug(TDEIO_SFTP_DB) << "sftpOpen(): handle (" << handle.size() << "): [" << handle << "]" << endl; - return SSH2_FX_OK; + finished(); } +void sftpProtocol::symlink(const TQString& target, const KURL& dest, bool overwrite) { + kdDebug(TDEIO_SFTP_DB) << "link " << target << "->" << dest.url() + << ", overwrite = " << overwrite << endl; -int sftpProtocol::sftpRead(const TQByteArray& handle, TDEIO::filesize_t offset, TQ_UINT32 len, TQByteArray& data) -{ - // kdDebug(TDEIO_SFTP_DB) << "sftpRead( offset = " << offset << ", len = " << len << ")" << endl; - TQByteArray p; - TQDataStream s(p, IO_WriteOnly); - - TQ_UINT32 id, expectedId; - id = expectedId = mMsgId++; - s << (TQ_UINT32)(1 /*type*/ + 4 /*id*/ + - 4 /*str length*/ + handle.size() + - 8 /*offset*/ + 4 /*length*/); - s << (TQ_UINT8)SSH2_FXP_READ; - s << (TQ_UINT32)id; - s << handle; - s << offset; // we don't have a convienient 64 bit int so set upper int to zero - s << len; - - putPacket(p); - getPacket(p); - - TQDataStream r(p, IO_ReadOnly); - TQ_UINT8 type; - - r >> type >> id; - if( id != expectedId ) { - kdError(TDEIO_SFTP_DB) << "sftpRead: sftp packet id mismatch" << endl; - return -1; - } - - if( type == SSH2_FXP_STATUS ) { - TQ_UINT32 errCode; - r >> errCode; - kdError(TDEIO_SFTP_DB) << "sftpRead: read failed with code " << errCode << endl; - return errCode; - } + openConnection(); + if (!mConnected) { + return; + } - if( type != SSH2_FXP_DATA ) { - kdError(TDEIO_SFTP_DB) << "sftpRead: unexpected message type of " << type << endl; - return -1; + TQByteArray t = target.utf8(); + TQByteArray d = dest.path().utf8(); + + bool failed = false; + if (sftp_symlink(mSftp, t.data(), d.data()) < 0) { + if (overwrite) { + sftp_attributes sb = sftp_lstat(mSftp, d.data()); + if (sb == NULL) { + failed = true; + } else { + if (sftp_unlink(mSftp, d.data()) < 0) { + failed = true; + } else { + if (sftp_symlink(mSftp, t.data(), d.data()) < 0) { + failed = true; + } + } + } + sftp_attributes_free(sb); } + } - r >> data; + if (failed) { + reportError(dest, sftp_get_error(mSftp)); + return; + } - return SSH2_FX_OK; + finished(); } +void sftpProtocol::chmod(const KURL& url, int permissions) { + kdDebug(TDEIO_SFTP_DB) << "change permission of " << url.url() << " to " << TQString::number(permissions) << endl; -int sftpProtocol::sftpWrite(const TQByteArray& handle, TDEIO::filesize_t offset, const TQByteArray& data){ -// kdDebug(TDEIO_SFTP_DB) << "sftpWrite( offset = " << offset << -// ", data sz = " << data.size() << ")" << endl; - TQByteArray p; - TQDataStream s(p, IO_WriteOnly); + openConnection(); + if (!mConnected) { + return; + } - TQ_UINT32 id, expectedId; - id = expectedId = mMsgId++; - s << (TQ_UINT32)(1 /*type*/ + 4 /*id*/ + - 4 /*str length*/ + handle.size() + - 8 /*offset*/ + - 4 /* data size */ + data.size()); - s << (TQ_UINT8)SSH2_FXP_WRITE; - s << (TQ_UINT32)id; - s << handle; - s << offset; // we don't have a convienient 64 bit int so set upper int to zero - s << data; + TQByteArray path = url.path().utf8(); -// kdDebug(TDEIO_SFTP_DB) << "sftpWrite(): SSH2_FXP_WRITE, id:" -// << id << ", handle:" << handle << ", offset:" << offset << ", some data" << endl; + if (sftp_chmod(mSftp, path.data(), permissions) < 0) { + reportError(url, sftp_get_error(mSftp)); + return; + } -// kdDebug(TDEIO_SFTP_DB) << "sftpWrite(): send packet [" << p << "]" << endl; + finished(); +} - putPacket(p); - getPacket(p); +void sftpProtocol::del(const KURL &url, bool isfile){ + kdDebug(TDEIO_SFTP_DB) << "deleting " << (isfile ? "file: " : "directory: ") << url.url() << endl; -// kdDebug(TDEIO_SFTP_DB) << "sftpWrite(): received packet [" << p << "]" << endl; + openConnection(); + if (!mConnected) { + return; + } - TQDataStream r(p, IO_ReadOnly); - TQ_UINT8 type; + TQByteArray path = url.path().utf8(); - r >> type >> id; - if( id != expectedId ) { - kdError(TDEIO_SFTP_DB) << "sftpWrite(): sftp packet id mismatch, got " - << id << ", expected " << expectedId << endl; - return -1; + if (isfile) { + if (sftp_unlink(mSftp, path.data()) < 0) { + reportError(url, sftp_get_error(mSftp)); + return; } - - if( type != SSH2_FXP_STATUS ) { - kdError(TDEIO_SFTP_DB) << "sftpWrite(): unexpected message type of " << type << endl; - return -1; + } else { + if (sftp_rmdir(mSftp, path.data()) < 0) { + reportError(url, sftp_get_error(mSftp)); + return; } + } - TQ_UINT32 code; - r >> code; - return code; + finished(); } +void sftpProtocol::slave_status() { + kdDebug(TDEIO_SFTP_DB) << "connected to " << mHost << "?: " << mConnected << endl; + slaveStatus((mConnected ? mHost : TQString()), mConnected); +} diff --git a/tdeioslave/sftp/tdeio_sftp.h b/tdeioslave/sftp/tdeio_sftp.h index 30c452f9b..88ddaecf1 100644 --- a/tdeioslave/sftp/tdeio_sftp.h +++ b/tdeioslave/sftp/tdeio_sftp.h @@ -1,37 +1,49 @@ -/*************************************************************************** - sftpProtocol.h - description - ------------------- - begin : Sat Jun 30 20:08:47 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. * - * * - ***************************************************************************/ +/* + * Copyright (c) 2001 Lucas Fisher + * Copyright (c) 2009 Andreas Schneider + * Copyright (c) 2020 Martin Sandsmark + * KDE2 port + * Copyright (c) 2022 Mavridis Philippe + * Trinity port + * + * 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. + */ + #ifndef __tdeio_sftp_h__ #define __tdeio_sftp_h__ -#include -#include -#include - #include #include #include #include +#include -#include "process.h" -#include "sftpfileattr.h" -#include "ksshprocess.h" +#include +#include +#include +// 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 TDEIO_SFTP_DB 7120 +namespace TDEIO { + class AuthInfo; +} class sftpProtocol : public TDEIO::SlaveBase { @@ -39,23 +51,35 @@ class sftpProtocol : public TDEIO::SlaveBase public: sftpProtocol(const TQCString &pool_socket, const TQCString &app_socket); virtual ~sftpProtocol(); - virtual void setHost(const TQString& h, int port, const TQString& user, const TQString& 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, bool overwrite); - virtual void put(const KURL& url, int permissions, bool overwrite, bool resume); - 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 TQString& target, const KURL& dest, bool overwrite); - virtual void rename(const KURL& src, const KURL& dest, bool overwrite); - virtual void mkdir(const KURL&url, int permissions); - virtual void openConnection(); + virtual void setHost(const TQString& h, int port, const TQString& user, const TQString& pass) override; + virtual void get(const KURL& url) override; + virtual void listDir(const KURL& url) override; + virtual void mimetype(const KURL& url) override; + virtual void stat(const KURL& url) override; + virtual void put(const KURL& url, int permissions, bool overwrite, bool resume) override; + virtual void copy(const KURL &src, const KURL &dest, int permissions, bool overwrite) override; + virtual void closeConnection() override; + virtual void slave_status() override; + virtual void del(const KURL &url, bool isfile) override; + virtual void chmod(const KURL& url, int permissions) override; + virtual void symlink(const TQString& target, const KURL& dest, bool overwrite) override; + virtual void rename(const KURL& src, const KURL& dest, bool overwrite) override; + virtual void mkdir(const KURL& url, int permissions) override; + virtual void openConnection() override; + + // 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); + + // libssh logging callback (note that this is called by the + // global ::log_callback() call. + void log_callback(ssh_session session, int priority, const char *message, + void *userdata); private: // Private variables + void statMime(const KURL &url); + void closeFile(); /** True if ioslave is connected to sftp server. */ bool mConnected; @@ -65,8 +89,11 @@ private: // Private variables /** 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 mSession; + + /** The sftp session for the connection */ + sftp_session mSftp; /** Username to use when connecting */ TQString mUsername; @@ -74,76 +101,34 @@ private: // Private variables /** User's password */ TQString 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; + + ssh_callbacks mCallbacks; /** Version of the sftp protocol we are using. */ int sftpVersion; - - struct Status - { - int code; - TDEIO::filesize_t size; - TQString text; - }; + + //struct Status + //{ + // int code; + // TDEIO::filesize_t size; + // TQString text; + //}; private: // private methods - bool getPacket(TQByteArray& 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); - - /** Send an sftp packet to stdin of the ssh process. */ - bool putPacket(TQByteArray& p); - /** Process SSH_FXP_STATUS packets. */ - void processStatus(TQ_UINT8, const TQString& message = TQString::null); - /** Process SSH_FXP_STATUS packes and return the result. */ - Status doProcessStatus(TQ_UINT8, const TQString& message = TQString::null); - /** Opens a directory handle for url.path. Returns true if succeeds. */ - int sftpOpenDirectory(const KURL& url, TQByteArray& handle); - /** Closes a directory or file handle. */ - int sftpClose(const TQByteArray& handle); - /** Send a sftp command to rename a file or directoy. */ - 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 TQString& target, const KURL& dest); - /** Get directory listings. */ - int sftpReadDir(const TQByteArray& handle, const KURL& url); - /** Retrieves the destination of a link. */ - int sftpReadLink(const KURL& url, TQString& target); - /** Stats a file. */ - int sftpStat(const KURL& url, sftpFileAttr& attr); - /** No descriptions */ - int sftpOpen(const KURL& url, const TQ_UINT32 pflags, const sftpFileAttr& attr, TQByteArray& handle); - /** No descriptions */ - int sftpRead(const TQByteArray& handle, TDEIO::filesize_t offset, TQ_UINT32 len, TQByteArray& data); - /** No descriptions */ - int sftpWrite(const TQByteArray& handle, TDEIO::filesize_t offset, const TQByteArray& data); - - /** Performs faster upload when the source is a local file... */ - void sftpCopyPut(const KURL& src, const KURL& dest, int mode, bool overwrite); - /** Performs faster download when the destination is a local file... */ - void sftpCopyGet(const KURL& dest, const KURL& src, int mode, bool overwrite); - - /** */ - Status sftpGet( const KURL& src, TDEIO::filesize_t offset = 0, int fd = -1); - void sftpPut( const KURL& dest, int permissions, bool resume, bool overwrite, int fd = -1); + + int authenticateKeyboardInteractive(TDEIO::AuthInfo &info); + + void reportError(const KURL &url, const int err); + + bool createUDSEntry(const TQString &filename, const TQByteArray &path, + TDEIO::UDSEntry &entry, short int details); + + TQString canonicalizePath(const TQString &path); }; + #endif