summaryrefslogtreecommitdiffstats
path: root/kioslave/sftp
diff options
context:
space:
mode:
Diffstat (limited to 'kioslave/sftp')
-rw-r--r--kioslave/sftp/AUTHORS3
-rw-r--r--kioslave/sftp/CHANGELOG59
-rw-r--r--kioslave/sftp/DEBUGGING12
-rw-r--r--kioslave/sftp/Makefile.am25
-rw-r--r--kioslave/sftp/TODO5
-rw-r--r--kioslave/sftp/atomicio.cpp67
-rw-r--r--kioslave/sftp/atomicio.h39
-rw-r--r--kioslave/sftp/kio_sftp.cpp2286
-rw-r--r--kioslave/sftp/kio_sftp.h149
-rw-r--r--kioslave/sftp/ksshprocess.cpp1104
-rw-r--r--kioslave/sftp/ksshprocess.h623
-rw-r--r--kioslave/sftp/ksshprocesstest.cpp98
-rw-r--r--kioslave/sftp/process.cpp493
-rw-r--r--kioslave/sftp/process.h148
-rw-r--r--kioslave/sftp/sftp.h91
-rw-r--r--kioslave/sftp/sftp.protocol84
-rw-r--r--kioslave/sftp/sftpfileattr.cpp346
-rw-r--r--kioslave/sftp/sftpfileattr.h261
18 files changed, 5893 insertions, 0 deletions
diff --git a/kioslave/sftp/AUTHORS b/kioslave/sftp/AUTHORS
new file mode 100644
index 000000000..c763d00bc
--- /dev/null
+++ b/kioslave/sftp/AUTHORS
@@ -0,0 +1,3 @@
+Dawit Alemayehu <adawit@kde.org>
+Lucas Fisher <ljfisher@iastate.edu>
+
diff --git a/kioslave/sftp/CHANGELOG b/kioslave/sftp/CHANGELOG
new file mode 100644
index 000000000..b41c17019
--- /dev/null
+++ b/kioslave/sftp/CHANGELOG
@@ -0,0 +1,59 @@
+- 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 KProcess 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? KProcess doesn't give us access to env variables.
+ - Our askpass program can probably talk to the kdesu 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 kio_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/kioslave/sftp/DEBUGGING b/kioslave/sftp/DEBUGGING
new file mode 100644
index 000000000..89ae8fe18
--- /dev/null
+++ b/kioslave/sftp/DEBUGGING
@@ -0,0 +1,12 @@
+DEBUGGING
+
+The best way to debug this slave is to send debug info to a
+file using 'kdebugDialog --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/kioslave/sftp/Makefile.am b/kioslave/sftp/Makefile.am
new file mode 100644
index 000000000..24ebc5aef
--- /dev/null
+++ b/kioslave/sftp/Makefile.am
@@ -0,0 +1,25 @@
+## Makefile.am of kdebase/kioslave/sftp
+
+INCLUDES = $(all_includes)
+AM_LDFLAGS = $(all_libraries) $(KDE_RPATH)
+METASOURCES = AUTO
+
+####### Files
+
+check_PROGRAMS = ksshprocesstest
+
+ksshprocesstest_SOURCES = ksshprocesstest.cpp
+ksshprocesstest_LDADD = $(LIB_KSYCOCA) ksshprocess.lo process.lo atomicio.lo
+
+kde_module_LTLIBRARIES = kio_sftp.la
+
+kio_sftp_la_SOURCES = process.cpp atomicio.cpp kio_sftp.cpp sftpfileattr.cpp ksshprocess.cpp
+kio_sftp_la_LIBADD = $(LIB_KIO)
+kio_sftp_la_LDFLAGS = $(all_libraries) -module $(KDE_PLUGIN)
+noinst_HEADERS = atomicio.h kio_sftp.h ksshprocess.h process.h sftpfileattr.h sftp.h
+
+kdelnk_DATA = sftp.protocol
+kdelnkdir = $(kde_servicesdir)
+
+messages:
+ $(XGETTEXT) *.cpp -o $(podir)/kio_sftp.pot
diff --git a/kioslave/sftp/TODO b/kioslave/sftp/TODO
new file mode 100644
index 000000000..0f1411317
--- /dev/null
+++ b/kioslave/sftp/TODO
@@ -0,0 +1,5 @@
+TODO:
+=====
+
+- Support for use of public keys, maybe ssh-agent, a key management app, etc.
+
diff --git a/kioslave/sftp/atomicio.cpp b/kioslave/sftp/atomicio.cpp
new file mode 100644
index 000000000..057f20fe9
--- /dev/null
+++ b/kioslave/sftp/atomicio.cpp
@@ -0,0 +1,67 @@
+/*
+ * 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 <unistd.h>
+#include <errno.h>
+#include <kdebug.h>
+
+/*
+ * 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/kioslave/sftp/atomicio.h b/kioslave/sftp/atomicio.h
new file mode 100644
index 000000000..4468757d5
--- /dev/null
+++ b/kioslave/sftp/atomicio.h
@@ -0,0 +1,39 @@
+#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 <sys/types.h>
+#include <unistd.h>
+
+/*
+ * 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/kioslave/sftp/kio_sftp.cpp b/kioslave/sftp/kio_sftp.cpp
new file mode 100644
index 000000000..e6aaaf532
--- /dev/null
+++ b/kioslave/sftp/kio_sftp.cpp
@@ -0,0 +1,2286 @@
+/***************************************************************************
+ 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 kdeinit has to run in a terminal.
+Ssh will detect this terminal and ask for a password there, but will just get garbage.
+So we can't connect.
+*/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <fcntl.h>
+
+#include <qcstring.h>
+#include <qstring.h>
+#include <qobject.h>
+#include <qstrlist.h>
+#include <qfile.h>
+#include <qbuffer.h>
+
+#include <stdlib.h>
+#include <unistd.h>
+#include <signal.h>
+#include <errno.h>
+#include <ctype.h>
+#include <time.h>
+#include <netdb.h>
+#include <string.h>
+
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <sys/time.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <sys/wait.h>
+
+#include <kapplication.h>
+#include <kuser.h>
+#include <kdebug.h>
+#include <kmessagebox.h>
+#include <kinstance.h>
+#include <kglobal.h>
+#include <kstandarddirs.h>
+#include <klocale.h>
+#include <kurl.h>
+#include <kio/ioslave_defaults.h>
+#include <kmimetype.h>
+#include <kmimemagic.h>
+#include <klargefile.h>
+#include <kremoteencoding.h>
+
+#include "sftp.h"
+#include "kio_sftp.h"
+#include "atomicio.h"
+#include "sftpfileattr.h"
+#include "ksshprocess.h"
+
+
+using namespace KIO;
+extern "C"
+{
+ int KDE_EXPORT kdemain( int argc, char **argv )
+ {
+ KInstance instance( "kio_sftp" );
+
+ kdDebug(KIO_SFTP_DB) << "*** Starting kio_sftp " << endl;
+
+ if (argc != 4) {
+ kdDebug(KIO_SFTP_DB) << "Usage: kio_sftp protocol domain-socket1 domain-socket2" << endl;
+ exit(-1);
+ }
+
+ sftpProtocol slave(argv[2], argv[3]);
+ slave.dispatchLoop();
+
+ kdDebug(KIO_SFTP_DB) << "*** kio_sftp Done" << endl;
+ return 0;
+ }
+}
+
+
+/*
+ * 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;
+ }
+ }
+ return 0;
+}
+
+sftpProtocol::sftpProtocol(const QCString &pool_socket, const QCString &app_socket)
+ : SlaveBase("kio_sftp", pool_socket, app_socket),
+ mConnected(false), mPort(-1), mMsgId(0) {
+ kdDebug(KIO_SFTP_DB) << "sftpProtocol(): pid = " << getpid() << endl;
+}
+
+
+sftpProtocol::~sftpProtocol() {
+ kdDebug(KIO_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(KIO_SFTP_DB) << "isSupportedOperation(type:"
+ << type << "): unrecognized operation type" << endl;
+ break;
+ }
+
+ return false;
+}
+
+void sftpProtocol::copy(const KURL &src, const KURL &dest, int permissions, bool overwrite)
+{
+ kdDebug(KIO_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, QString::null);
+}
+
+void sftpProtocol::sftpCopyGet(const KURL& dest, const KURL& src, int mode, bool overwrite)
+{
+ kdDebug(KIO_SFTP_DB) << "sftpCopyGet(): " << src << " -> " << dest << endl;
+
+ // Attempt to establish a connection...
+ openConnection();
+ if( !mConnected )
+ return;
+
+ KDE_struct_stat buff_orig;
+ QCString dest_orig ( QFile::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;
+ }
+ }
+
+ KIO::filesize_t offset = 0;
+ QCString 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(KIO_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(KIO_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 ??
+ KIO::filesize_t size = config()->readNumEntry("MinimumKeepSize", DEFAULT_MINIMUM_KEEP_SIZE);
+ if (info.size < size)
+ ::remove(dest_part.data());
+
+ error(info.code, info.text);
+ return;
+ }
+
+ if (::close(fd) != 0)
+ {
+ error(ERR_COULD_NOT_WRITE, dest.prettyURL());
+ return;
+ }
+
+ //
+ if (markPartial)
+ {
+ if (::rename(dest_part.data(), dest_orig.data()) != 0)
+ {
+ error (ERR_CANNOT_RENAME_PARTIAL, dest_part);
+ return;
+ }
+ }
+
+ data(QByteArray());
+ kdDebug(KIO_SFTP_DB) << "sftpCopyGet(): emit finished()" << endl;
+ finished();
+}
+
+sftpProtocol::Status sftpProtocol::sftpGet( const KURL& src, KIO::filesize_t offset, int fd )
+{
+ int code;
+ sftpFileAttr attr(remoteEncoding());
+
+ Status res;
+ res.code = 0;
+ res.size = 0;
+
+ kdDebug(KIO_SFTP_DB) << "sftpGet(): " << src << endl;
+
+ // stat the file first to get its size
+ if( (code = sftpStat(src, attr)) != SSH2_FX_OK ) {
+ return doProcessStatus(code, src.prettyURL());
+ }
+
+ // 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;
+ }
+
+ KIO::filesize_t fileSize = attr.fileSize();
+ Q_UINT32 pflags = SSH2_FXF_READ;
+ attr.clear();
+
+ QByteArray handle;
+ if( (code = sftpOpen(src, pflags, attr, handle)) != SSH2_FX_OK ) {
+ res.text = src.prettyURL();
+ res.code = ERR_CANNOT_OPEN_FOR_READING;
+ return res;
+ }
+
+ // needed for determining mimetype
+ // note: have to emit mimetype before emitting totalsize.
+ QByteArray buff;
+ QByteArray 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.
+ Q_UINT32 len = 60*1024;
+ code = SSH2_FX_OK;
+
+ kdDebug(KIO_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(KIO_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);
+ }
+ }
+
+ /*
+ 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;
+ }
+ }
+
+ 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?
+ }
+
+ res.size = offset;
+ sftpClose(handle);
+ processedSize (offset);
+ return res;
+}
+
+void sftpProtocol::get(const KURL& url) {
+ kdDebug(KIO_SFTP_DB) << "get(): " << url << endl ;
+
+ openConnection();
+ if( !mConnected )
+ return;
+
+ // Get resume offset
+ Q_UINT64 offset = config()->readUnsignedLongNumEntry("resume");
+ if( offset > 0 ) {
+ canResume();
+ kdDebug(KIO_SFTP_DB) << "get(): canResume(), offset = " << offset << endl;
+ }
+
+ Status info = sftpGet(url, offset);
+
+ if (info.code != 0)
+ {
+ error(info.code, info.text);
+ return;
+ }
+
+ data(QByteArray());
+ kdDebug(KIO_SFTP_DB) << "get(): emit finished()" << endl;
+ finished();
+}
+
+
+void sftpProtocol::setHost (const QString& h, int port, const QString& user, const QString& pass)
+{
+ kdDebug(KIO_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 {
+ struct servent *pse;
+ if( (pse = getservbyname("ssh", "tcp") ) == NULL )
+ mPort = 22;
+ else
+ mPort = ntohs(pse->s_port);
+ }
+
+ mUsername = user;
+ mPassword = pass;
+
+ if (user.isEmpty())
+ {
+ KUser u;
+ mUsername = u.loginName();
+ }
+}
+
+
+void sftpProtocol::openConnection() {
+
+ if(mConnected)
+ return;
+
+ kdDebug(KIO_SFTP_DB) << "openConnection(): " << mUsername << "@"
+ << mHost << ":" << mPort << endl;
+
+ infoMessage( i18n("Opening SFTP connection to host <b>%1:%2</b>").arg(mHost).arg(mPort));
+
+ if( mHost.isEmpty() ) {
+ kdDebug(KIO_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 + ":" + QString::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(KIO_SFTP_DB) << "openConnection(): checking cache "
+ << "info.username = " << info.username
+ << ", info.url = " << info.url.prettyURL() << endl;
+
+ if( checkCachedAuthentication(info) ) {
+ mUsername = info.username;
+ mPassword = info.password;
+ }
+ }
+
+ ///////////////////////////////////////////////////////////////////////////
+ // 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
+ QString msg; // msg for dialog box
+ QString caption; // dialog box caption
+ bool firstTime = true;
+ bool dlgResult;
+
+ while( !(mConnected = ssh.connect()) ) {
+ err = ssh.error();
+ kdDebug(KIO_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(KIO_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, QString::null);
+ kdDebug(KIO_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(KIO_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(KIO_SFTP_DB) << "openConnection(): "
+ "Adding username to options list" << endl;
+ opt.opt = KSshProcess::SSH_USERNAME;
+ usernameIt = opts.append(opt);
+ }
+
+ if( passwdIt == KSshProcess::SshOptListIterator() ) {
+ kdDebug(KIO_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, QString::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, QString::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;
+ }
+ }
+
+ // catch all in case we did something wrong above
+ if( !mConnected ) {
+ error(ERR_INTERNAL, QString::null);
+ return;
+ }
+
+ // Now send init packet.
+ kdDebug(KIO_SFTP_DB) << "openConnection(): Sending SSH2_FXP_INIT packet." << endl;
+ QByteArray p;
+ QDataStream packet(p, IO_WriteOnly);
+ packet << (Q_UINT32)5; // packet length
+ packet << (Q_UINT8) SSH2_FXP_INIT; // packet type
+ packet << (Q_UINT32)SSH2_FILEXFER_VERSION; // client version
+
+ putPacket(p);
+ getPacket(p);
+
+ QDataStream s(p, IO_ReadOnly);
+ Q_UINT32 version;
+ Q_UINT8 type;
+ s >> type;
+ kdDebug(KIO_SFTP_DB) << "openConnection(): Got type " << type << endl;
+
+ if( type == SSH2_FXP_VERSION ) {
+ s >> version;
+ kdDebug(KIO_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(KIO_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(KIO_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;
+ QCString file (QFile::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(KIO_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(KIO_SFTP_DB) << "sftpPut(): <file> 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;
+ }
+ }
+ 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" );
+
+ Q_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(KIO_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(KIO_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;
+ }
+ }
+
+ // Determine the url we will actually write to...
+ KURL writeUrl (markPartial ? partUrl:origUrl);
+
+ Q_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());
+ QByteArray 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;
+ }
+
+ if( code == SSH2_FX_FAILURE ) { // assume failure means file exists
+ error(ERR_FILE_ALREADY_EXIST, writeUrl.prettyURL());
+ return;
+ }
+ else {
+ processStatus(code, writeUrl.prettyURL());
+ return;
+ }
+ }
+
+ long nbytes;
+ QByteArray 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;
+ }
+ }
+
+ } while( nbytes > 0 );
+
+ if( nbytes < 0 ) {
+ sftpClose(handle);
+
+ if( markPartial ) {
+ // Remove remote file if it smaller than our keep size
+ uint minKeepSize = config()->readNumEntry("MinimumKeepSize", DEFAULT_MINIMUM_KEEP_SIZE);
+
+ if( sftpStat(writeUrl, attr) == SSH2_FX_OK ) {
+ if( attr.fileSize() < minKeepSize ) {
+ sftpRemove(writeUrl, true);
+ }
+ }
+ }
+
+ error( ERR_UNKNOWN, i18n("Unknown error was encountered while copying the file "
+ "to '%1'. Please try again.").arg(dest.host()) );
+ return;
+ }
+
+ if( (code = sftpClose(handle)) != SSH2_FX_OK ) {
+ error(ERR_COULD_NOT_WRITE, writeUrl.prettyURL());
+ return;
+ }
+
+ // If wrote to a partial file, then remove the part ext
+ if( markPartial ) {
+ if( sftpRename(partUrl, origUrl) != SSH2_FX_OK ) {
+ error(ERR_CANNOT_RENAME_PARTIAL, origUrl.prettyURL());
+ return;
+ }
+ }
+
+ finished();
+}
+
+void sftpProtocol::put ( const KURL& url, int permissions, bool overwrite, bool resume ){
+ kdDebug(KIO_SFTP_DB) << "put(): " << url << ", overwrite = " << overwrite
+ << ", resume = " << resume << endl;
+
+ sftpPut( url, permissions, resume, overwrite );
+}
+
+void sftpProtocol::stat ( const KURL& url ){
+ kdDebug(KIO_SFTP_DB) << "stat(): " << url << endl;
+
+ 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 = KIO::UDS_NAME;
+ atom.m_str = QString::null;
+ entry.append( atom );
+
+ atom.m_uds = KIO::UDS_FILE_TYPE;
+ atom.m_long = S_IFDIR;
+ entry.append( atom );
+
+ atom.m_uds = KIO::UDS_ACCESS;
+ atom.m_long = S_IRUSR | S_IXUSR | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH;
+ entry.append( atom );
+
+ atom.m_uds = KIO::UDS_USER;
+ atom.m_str = mUsername;
+ entry.append( atom );
+ atom.m_uds = KIO::UDS_GROUP;
+ entry.append( atom );
+
+ // no size
+ statEntry( entry );
+ finished();
+ return;
+ }
+
+ 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(KIO_SFTP_DB) << "stat: END" << endl;
+ return;
+}
+
+
+void sftpProtocol::mimetype ( const KURL& url ){
+ kdDebug(KIO_SFTP_DB) << "mimetype(): " << url << endl;
+
+ openConnection();
+ if( !mConnected )
+ return;
+
+ Q_UINT32 pflags = SSH2_FXF_READ;
+ QByteArray 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;
+ }
+
+ Q_UINT32 len = 1024; // Get first 1k for determining mimetype
+ Q_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);
+
+ kdDebug(KIO_SFTP_DB) << "mimetype(): offset = " << offset << endl;
+ }
+ }
+
+
+ data(QByteArray());
+ processedSize(offset);
+ sftpClose(handle);
+ finished();
+ kdDebug(KIO_SFTP_DB) << "mimetype(): END" << endl;
+}
+
+
+void sftpProtocol::listDir(const KURL& url) {
+ kdDebug(KIO_SFTP_DB) << "listDir(): " << url << endl;
+
+ openConnection();
+ if( !mConnected )
+ return;
+
+ if( !url.hasPath() ) {
+ KURL newUrl ( url );
+ if( sftpRealPath(url, newUrl) == SSH2_FX_OK ) {
+ kdDebug(KIO_SFTP_DB) << "listDir: Redirecting to " << newUrl << endl;
+ redirection(newUrl);
+ finished();
+ return;
+ }
+ }
+
+ int code;
+ QByteArray handle;
+
+ if( (code = sftpOpenDirectory(url, handle)) != SSH2_FX_OK ) {
+ kdError(KIO_SFTP_DB) << "listDir(): open directory failed" << endl;
+ processStatus(code, url.prettyURL());
+ return;
+ }
+
+
+ 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(KIO_SFTP_DB) << "listDir(): return code = " << code << endl;
+ }
+
+ if( (code = sftpClose(handle)) != SSH2_FX_OK ) {
+ kdError(KIO_SFTP_DB) << "listdir(): closing of directory failed" << endl;
+ processStatus(code, url.prettyURL());
+ return;
+ }
+
+ finished();
+ kdDebug(KIO_SFTP_DB) << "listDir(): END" << endl;
+}
+
+/** 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(KIO_SFTP_DB) << "mkdir() creating dir: " << url.path() << endl;
+
+ openConnection();
+ if( !mConnected )
+ return;
+
+ QCString path = remoteEncoding()->encode(url.path());
+ uint len = path.length();
+
+ sftpFileAttr attr(remoteEncoding());
+
+ if (permissions != -1)
+ attr.setPermissions(permissions);
+
+ Q_UINT32 id, expectedId;
+ id = expectedId = mMsgId++;
+
+ QByteArray p;
+ QDataStream s(p, IO_WriteOnly);
+ s << Q_UINT32(1 /*type*/ + 4 /*id*/ + 4 /*str length*/ + len + attr.size());
+ s << (Q_UINT8)SSH2_FXP_MKDIR;
+ s << id;
+ s.writeBytes(path.data(), len);
+ s << attr;
+
+ kdDebug(KIO_SFTP_DB) << "mkdir(): packet size is " << p.size() << endl;
+
+ putPacket(p);
+ getPacket(p);
+
+ Q_UINT8 type;
+ QDataStream r(p, IO_ReadOnly);
+
+ r >> type >> id;
+ if( id != expectedId ) {
+ kdError(KIO_SFTP_DB) << "mkdir: sftp packet id mismatch" << endl;
+ error(ERR_COULD_NOT_MKDIR, path);
+ finished();
+ return;
+ }
+
+ if( type != SSH2_FXP_STATUS ) {
+ kdError(KIO_SFTP_DB) << "mkdir(): unexpected packet type of " << type << endl;
+ error(ERR_COULD_NOT_MKDIR, path);
+ finished();
+ return;
+ }
+
+ int code;
+ r >> code;
+ if( code != SSH2_FX_OK ) {
+ kdError(KIO_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;
+ }
+
+ error(ERR_COULD_NOT_MKDIR, path);
+ }
+
+ finished();
+}
+
+void sftpProtocol::rename(const KURL& src, const KURL& dest, bool overwrite){
+ kdDebug(KIO_SFTP_DB) << "rename(" << src << " -> " << dest << ")" << endl;
+
+ if (!isSupportedOperation(SSH2_FXP_RENAME)) {
+ error(ERR_UNSUPPORTED_ACTION,
+ i18n("The remote host does not support renaming files."));
+ return;
+ }
+
+ openConnection();
+ if( !mConnected )
+ return;
+
+ // Always stat the destination before attempting to rename
+ // a file or a directory...
+ sftpFileAttr attr(remoteEncoding());
+ int code = sftpStat(dest, attr);
+
+ // If the destination directory, exists tell it to the job
+ // so it the proper action can be presented to the user...
+ if( code == SSH2_FX_OK )
+ {
+ if (!overwrite)
+ {
+ if ( S_ISDIR(attr.permissions()) )
+ error( KIO::ERR_DIR_ALREADY_EXIST, dest.url() );
+ else
+ error( KIO::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;
+ }
+ }
+
+ // Do the renaming...
+ if( (code = sftpRename(src, dest)) != SSH2_FX_OK ) {
+ processStatus(code);
+ return;
+ }
+
+ finished();
+ kdDebug(KIO_SFTP_DB) << "rename(): END" << endl;
+}
+
+void sftpProtocol::symlink(const QString& target, const KURL& dest, bool overwrite){
+ kdDebug(KIO_SFTP_DB) << "symlink()" << endl;
+
+ if (!isSupportedOperation(SSH2_FXP_SYMLINK)) {
+ error(ERR_UNSUPPORTED_ACTION,
+ i18n("The remote host does not support creating symbolic links."));
+ return;
+ }
+
+ openConnection();
+ if( !mConnected )
+ return;
+
+ int code;
+ bool failed = false;
+ if( (code = sftpSymLink(target, dest)) != SSH2_FX_OK ) {
+ if( 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){
+ QString perms;
+ perms.setNum(permissions, 8);
+ kdDebug(KIO_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(KIO_SFTP_DB) << "chmod(): sftpSetStat failed with error " << code << endl;
+ if( code == SSH2_FX_FAILURE )
+ error(ERR_CANNOT_CHMOD, QString::null);
+ else
+ processStatus(code, url.prettyURL());
+ }
+ finished();
+}
+
+
+void sftpProtocol::del(const KURL &url, bool isfile){
+ kdDebug(KIO_SFTP_DB) << "del(" << url << ", " << (isfile?"file":"dir") << ")" << endl;
+
+ openConnection();
+ if( !mConnected )
+ return;
+
+ int code;
+ if( (code = sftpRemove(url, isfile)) != SSH2_FX_OK ) {
+ kdError(KIO_SFTP_DB) << "del(): sftpRemove failed with error code " << code << endl;
+ processStatus(code, url.prettyURL());
+ }
+ finished();
+}
+
+void sftpProtocol::slave_status() {
+ kdDebug(KIO_SFTP_DB) << "slave_status(): connected to "
+ << mHost << "? " << mConnected << endl;
+
+ slaveStatus ((mConnected ? mHost : QString::null), mConnected);
+}
+
+bool sftpProtocol::getPacket(QByteArray& msg) {
+ QByteArray buf(4096);
+
+ // Get the message length...
+ ssize_t len = atomicio(ssh.stdioFd(), buf.data(), 4, true /*read*/);
+
+ if( len == 0 || len == -1 ) {
+ kdDebug(KIO_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;
+ }
+
+ uint msgLen;
+ QDataStream s(buf, IO_ReadOnly);
+ s >> msgLen;
+
+ //kdDebug(KIO_SFTP_DB) << "getPacket(): Message size = " << msgLen << endl;
+
+ msg.resize(0);
+
+ QBuffer b( msg );
+ b.open( IO_WriteOnly );
+
+ while( msgLen ) {
+ len = atomicio(ssh.stdioFd(), buf.data(), kMin(buf.size(), msgLen), true /*read*/);
+
+ if( len == 0 || len == -1) {
+ QString errmsg;
+ if (len == 0)
+ errmsg = i18n("Connection closed");
+ else
+ errmsg = i18n("Could not read SFTP packet");
+ kdDebug(KIO_SFTP_DB) << "getPacket(): nothing to read, ret = " <<
+ len << ", error =" << strerror(errno) << endl;
+ closeConnection();
+ error(ERR_CONNECTION_BROKEN, errmsg);
+ b.close();
+ return false;
+ }
+
+ b.writeBlock(buf.data(), len);
+
+ //kdDebug(KIO_SFTP_DB) << "getPacket(): Read Message size = " << len << endl;
+ //kdDebug(KIO_SFTP_DB) << "getPacket(): Copy Message size = " << msg.size() << endl;
+
+ msgLen -= len;
+ }
+
+ b.close();
+
+ return true;
+}
+
+/** Send an sftp packet to stdin of the ssh process. */
+bool sftpProtocol::putPacket(QByteArray& p){
+// kdDebug(KIO_SFTP_DB) << "putPacket(): size == " << p.size() << endl;
+ int ret;
+ ret = atomicio(ssh.stdioFd(), p.data(), p.size(), false /*write*/);
+ if( ret <= 0 ) {
+ kdDebug(KIO_SFTP_DB) << "putPacket(): write failed, ret =" << ret <<
+ ", error = " << strerror(errno) << endl;
+ return false;
+ }
+
+ return true;
+}
+
+/** 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){
+
+ kdDebug(KIO_SFTP_DB) << "sftpRealPath(" << url << ", newUrl)" << endl;
+
+ QCString path = remoteEncoding()->encode(url.path());
+ uint len = path.length();
+
+ Q_UINT32 id, expectedId;
+ id = expectedId = mMsgId++;
+
+ QByteArray p;
+ QDataStream s(p, IO_WriteOnly);
+ s << Q_UINT32(1 /*type*/ + 4 /*id*/ + 4 /*str length*/ + len);
+ s << (Q_UINT8)SSH2_FXP_REALPATH;
+ s << id;
+ s.writeBytes(path.data(), len);
+
+ putPacket(p);
+ getPacket(p);
+
+ Q_UINT8 type;
+ QDataStream r(p, IO_ReadOnly);
+
+ r >> type >> id;
+ if( id != expectedId ) {
+ kdError(KIO_SFTP_DB) << "sftpRealPath: sftp packet id mismatch" << endl;
+ return -1;
+ }
+
+ if( type == SSH2_FXP_STATUS ) {
+ Q_UINT32 code;
+ r >> code;
+ return code;
+ }
+
+ if( type != SSH2_FXP_NAME ) {
+ kdError(KIO_SFTP_DB) << "sftpRealPath(): unexpected packet type of " << type << endl;
+ return -1;
+ }
+
+ Q_UINT32 count;
+ r >> count;
+ if( count != 1 ) {
+ kdError(KIO_SFTP_DB) << "sftpRealPath(): Bad number of file attributes for realpath command" << endl;
+ return -1;
+ }
+
+ QCString newPath;
+ r >> newPath;
+
+ newPath.truncate(newPath.size());
+ if (newPath.isEmpty())
+ newPath = "/";
+ newUrl.setPath(newPath);
+
+ return SSH2_FX_OK;
+}
+
+sftpProtocol::Status sftpProtocol::doProcessStatus(Q_UINT8 code, const QString& 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;
+}
+
+/** Process SSH_FXP_STATUS packets. */
+void sftpProtocol::processStatus(Q_UINT8 code, const QString& message){
+ Status st = doProcessStatus( code, message );
+ if( st.code != 0 )
+ error( st.code, st.text );
+}
+
+/** Opens a directory handle for url.path. Returns true if succeeds. */
+int sftpProtocol::sftpOpenDirectory(const KURL& url, QByteArray& handle){
+
+ kdDebug(KIO_SFTP_DB) << "sftpOpenDirectory(" << url << ", handle)" << endl;
+
+ QCString path = remoteEncoding()->encode(url.path());
+ uint len = path.length();
+
+ Q_UINT32 id, expectedId;
+ id = expectedId = mMsgId++;
+
+ QByteArray p;
+ QDataStream s(p, IO_WriteOnly);
+ s << (Q_UINT32)(1 /*type*/ + 4 /*id*/ + 4 /*str length*/ + len);
+ s << (Q_UINT8)SSH2_FXP_OPENDIR;
+ s << (Q_UINT32)id;
+ s.writeBytes(path.data(), len);
+
+ putPacket(p);
+ getPacket(p);
+
+ QDataStream r(p, IO_ReadOnly);
+ Q_UINT8 type;
+
+ r >> type >> id;
+ if( id != expectedId ) {
+ kdError(KIO_SFTP_DB) << "sftpOpenDirectory: sftp packet id mismatch: " <<
+ "expected " << expectedId << ", got " << id << endl;
+ return -1;
+ }
+
+ if( type == SSH2_FXP_STATUS ) {
+ Q_UINT32 errCode;
+ r >> errCode;
+ return errCode;
+ }
+
+ if( type != SSH2_FXP_HANDLE ) {
+ kdError(KIO_SFTP_DB) << "sftpOpenDirectory: unexpected message type of " << type << endl;
+ return -1;
+ }
+
+ r >> handle;
+ if( handle.size() > 256 ) {
+ kdError(KIO_SFTP_DB) << "sftpOpenDirectory: handle exceeds max length" << endl;
+ return -1;
+ }
+
+ kdDebug(KIO_SFTP_DB) << "sftpOpenDirectory: handle (" << handle.size() << "): [" << handle << "]" << endl;
+ return SSH2_FX_OK;
+}
+
+/** Closes a directory or file handle. */
+int sftpProtocol::sftpClose(const QByteArray& handle){
+
+ kdDebug(KIO_SFTP_DB) << "sftpClose()" << endl;
+
+ Q_UINT32 id, expectedId;
+ id = expectedId = mMsgId++;
+
+ QByteArray p;
+ QDataStream s(p, IO_WriteOnly);
+ s << (Q_UINT32)(1 /*type*/ + 4 /*id*/ + 4 /*str length*/ + handle.size());
+ s << (Q_UINT8)SSH2_FXP_CLOSE;
+ s << (Q_UINT32)id;
+ s << handle;
+
+ putPacket(p);
+ getPacket(p);
+
+ QDataStream r(p, IO_ReadOnly);
+ Q_UINT8 type;
+
+ r >> type >> id;
+ if( id != expectedId ) {
+ kdError(KIO_SFTP_DB) << "sftpClose: sftp packet id mismatch" << endl;
+ return -1;
+ }
+
+ if( type != SSH2_FXP_STATUS ) {
+ kdError(KIO_SFTP_DB) << "sftpClose: unexpected message type of " << type << endl;
+ return -1;
+ }
+
+ Q_UINT32 code;
+ r >> code;
+ if( code != SSH2_FX_OK ) {
+ kdError(KIO_SFTP_DB) << "sftpClose: close failed with err code " << code << endl;
+ }
+
+ return code;
+}
+
+/** Set a files attributes. */
+int sftpProtocol::sftpSetStat(const KURL& url, const sftpFileAttr& attr){
+
+ kdDebug(KIO_SFTP_DB) << "sftpSetStat(" << url << ", attr)" << endl;
+
+ QCString path = remoteEncoding()->encode(url.path());
+ uint len = path.length();
+
+ Q_UINT32 id, expectedId;
+ id = expectedId = mMsgId++;
+
+ QByteArray p;
+ QDataStream s(p, IO_WriteOnly);
+ s << (Q_UINT32)(1 /*type*/ + 4 /*id*/ + 4 /*str length*/ + len + attr.size());
+ s << (Q_UINT8)SSH2_FXP_SETSTAT;
+ s << (Q_UINT32)id;
+ s.writeBytes(path.data(), len);
+ s << attr;
+
+ putPacket(p);
+ getPacket(p);
+
+ QDataStream r(p, IO_ReadOnly);
+ Q_UINT8 type;
+
+ r >> type >> id;
+ if( id != expectedId ) {
+ kdError(KIO_SFTP_DB) << "sftpSetStat(): sftp packet id mismatch" << endl;
+ return -1;
+ // XXX How do we do a fatal error?
+ }
+
+ if( type != SSH2_FXP_STATUS ) {
+ kdError(KIO_SFTP_DB) << "sftpSetStat(): unexpected message type of " << type << endl;
+ return -1;
+ }
+
+ Q_UINT32 code;
+ r >> code;
+ if( code != SSH2_FX_OK ) {
+ kdError(KIO_SFTP_DB) << "sftpSetStat(): set stat failed with err code " << code << endl;
+ }
+
+ return code;
+}
+
+/** Sends a sftp command to remove a file or directory. */
+int sftpProtocol::sftpRemove(const KURL& url, bool isfile){
+
+ kdDebug(KIO_SFTP_DB) << "sftpRemove(): " << url << ", isFile ? " << isfile << endl;
+
+ QCString path = remoteEncoding()->encode(url.path());
+ uint len = path.length();
+
+ Q_UINT32 id, expectedId;
+ id = expectedId = mMsgId++;
+
+ QByteArray p;
+ QDataStream s(p, IO_WriteOnly);
+ s << (Q_UINT32)(1 /*type*/ + 4 /*id*/ + 4 /*str length*/ + len);
+ s << (Q_UINT8)(isfile ? SSH2_FXP_REMOVE : SSH2_FXP_RMDIR);
+ s << (Q_UINT32)id;
+ s.writeBytes(path.data(), len);
+
+ putPacket(p);
+ getPacket(p);
+
+ QDataStream r(p, IO_ReadOnly);
+ Q_UINT8 type;
+
+ r >> type >> id;
+ if( id != expectedId ) {
+ kdError(KIO_SFTP_DB) << "del(): sftp packet id mismatch" << endl;
+ return -1;
+ }
+
+ if( type != SSH2_FXP_STATUS ) {
+ kdError(KIO_SFTP_DB) << "del(): unexpected message type of " << type << endl;
+ return -1;
+ }
+
+ Q_UINT32 code;
+ r >> code;
+ if( code != SSH2_FX_OK ) {
+ kdError(KIO_SFTP_DB) << "del(): del failed with err code " << code << endl;
+ }
+
+ return code;
+}
+
+/** Send a sftp command to rename a file or directoy. */
+int sftpProtocol::sftpRename(const KURL& src, const KURL& dest){
+
+ kdDebug(KIO_SFTP_DB) << "sftpRename(" << src << " -> " << dest << ")" << endl;
+
+ QCString srcPath = remoteEncoding()->encode(src.path());
+ QCString destPath = remoteEncoding()->encode(dest.path());
+
+ uint slen = srcPath.length();
+ uint dlen = destPath.length();
+
+ Q_UINT32 id, expectedId;
+ id = expectedId = mMsgId++;
+
+ QByteArray p;
+ QDataStream s(p, IO_WriteOnly);
+ s << (Q_UINT32)(1 /*type*/ + 4 /*id*/ +
+ 4 /*str length*/ + slen +
+ 4 /*str length*/ + dlen);
+ s << (Q_UINT8)SSH2_FXP_RENAME;
+ s << (Q_UINT32)id;
+ s.writeBytes(srcPath.data(), slen);
+ s.writeBytes(destPath.data(), dlen);
+
+ putPacket(p);
+ getPacket(p);
+
+ QDataStream r(p, IO_ReadOnly);
+ Q_UINT8 type;
+
+ r >> type >> id;
+ if( id != expectedId ) {
+ kdError(KIO_SFTP_DB) << "sftpRename(): sftp packet id mismatch" << endl;
+ return -1;
+ }
+
+ if( type != SSH2_FXP_STATUS ) {
+ kdError(KIO_SFTP_DB) << "sftpRename(): unexpected message type of " << type << endl;
+ return -1;
+ }
+
+ int code;
+ r >> code;
+ if( code != SSH2_FX_OK ) {
+ kdError(KIO_SFTP_DB) << "sftpRename(): rename failed with err code " << code << endl;
+ }
+
+ return code;
+}
+/** Get directory listings. */
+int sftpProtocol::sftpReadDir(const QByteArray& handle, const KURL& url){
+ // url is needed so we can lookup the link destination
+ kdDebug(KIO_SFTP_DB) << "sftpReadDir(): " << url << endl;
+
+ Q_UINT32 id, expectedId, count;
+ Q_UINT8 type;
+
+ sftpFileAttr attr (remoteEncoding());
+ attr.setDirAttrsFlag(true);
+
+ QByteArray p;
+ QDataStream s(p, IO_WriteOnly);
+ id = expectedId = mMsgId++;
+ s << (Q_UINT32)(1 /*type*/ + 4 /*id*/ + 4 /*str length*/ + handle.size());
+ s << (Q_UINT8)SSH2_FXP_READDIR;
+ s << (Q_UINT32)id;
+ s << handle;
+
+ putPacket(p);
+ getPacket(p);
+
+ QDataStream r(p, IO_ReadOnly);
+ r >> type >> id;
+
+ if( id != expectedId ) {
+ kdError(KIO_SFTP_DB) << "sftpReadDir(): sftp packet id mismatch" << endl;
+ return -1;
+ }
+
+ int code;
+ if( type == SSH2_FXP_STATUS ) {
+ r >> code;
+ return code;
+ }
+
+ if( type != SSH2_FXP_NAME ) {
+ kdError(KIO_SFTP_DB) << "kio_sftpProtocl::sftpReadDir(): Unexpected message" << endl;
+ return -1;
+ }
+
+ r >> count;
+ kdDebug(KIO_SFTP_DB) << "sftpReadDir(): got " << count << " entries" << endl;
+
+ while(count--) {
+ r >> attr;
+
+ if( S_ISLNK(attr.permissions()) ) {
+ KURL myurl ( url );
+ myurl.addPath(attr.filename());
+
+ // Stat the symlink to find out its type...
+ sftpFileAttr attr2 (remoteEncoding());
+ (void) sftpStat(myurl, attr2);
+
+ attr.setLinkType(attr2.linkType());
+ attr.setLinkDestination(attr2.linkDestination());
+ }
+
+ listEntry(attr.entry(), false);
+ }
+
+ listEntry(attr.entry(), true);
+
+ return SSH2_FX_OK;
+}
+
+int sftpProtocol::sftpReadLink(const KURL& url, QString& target){
+
+ kdDebug(KIO_SFTP_DB) << "sftpReadLink(): " << url << endl;
+
+ QCString path = remoteEncoding()->encode(url.path());
+ uint len = path.length();
+
+ //kdDebug(KIO_SFTP_DB) << "sftpReadLink(): Encoded Path: " << path << endl;
+ //kdDebug(KIO_SFTP_DB) << "sftpReadLink(): Encoded Size: " << len << endl;
+
+ Q_UINT32 id, expectedId;
+ id = expectedId = mMsgId++;
+
+ QByteArray p;
+ QDataStream s(p, IO_WriteOnly);
+ s << (Q_UINT32)(1 /*type*/ + 4 /*id*/ + 4 /*str length*/ + len);
+ s << (Q_UINT8)SSH2_FXP_READLINK;
+ s << id;
+ s.writeBytes(path.data(), len);
+
+
+ putPacket(p);
+ getPacket(p);
+
+ Q_UINT8 type;
+ QDataStream r(p, IO_ReadOnly);
+
+ r >> type >> id;
+ if( id != expectedId ) {
+ kdError(KIO_SFTP_DB) << "sftpReadLink(): sftp packet id mismatch" << endl;
+ return -1;
+ }
+
+ if( type == SSH2_FXP_STATUS ) {
+ Q_UINT32 code;
+ r >> code;
+ kdDebug(KIO_SFTP_DB) << "sftpReadLink(): read link failed with code " << code << endl;
+ return code;
+ }
+
+ if( type != SSH2_FXP_NAME ) {
+ kdError(KIO_SFTP_DB) << "sftpReadLink(): unexpected packet type of " << type << endl;
+ return -1;
+ }
+
+ Q_UINT32 count;
+ r >> count;
+ if( count != 1 ) {
+ kdError(KIO_SFTP_DB) << "sftpReadLink(): Bad number of file attributes for realpath command" << endl;
+ return -1;
+ }
+
+ QCString linkAddress;
+ r >> linkAddress;
+
+ linkAddress.truncate(linkAddress.size());
+ kdDebug(KIO_SFTP_DB) << "sftpReadLink(): Link address: " << linkAddress << endl;
+
+ target = remoteEncoding()->decode(linkAddress);
+
+ return SSH2_FX_OK;
+}
+
+int sftpProtocol::sftpSymLink(const QString& _target, const KURL& dest){
+
+ QCString destPath = remoteEncoding()->encode(dest.path());
+ QCString target = remoteEncoding()->encode(_target);
+ uint dlen = destPath.length();
+ uint tlen = target.length();
+
+ kdDebug(KIO_SFTP_DB) << "sftpSymLink(" << target << " -> " << destPath << ")" << endl;
+
+ Q_UINT32 id, expectedId;
+ id = expectedId = mMsgId++;
+
+ QByteArray p;
+ QDataStream s(p, IO_WriteOnly);
+ s << (Q_UINT32)(1 /*type*/ + 4 /*id*/ +
+ 4 /*str length*/ + tlen +
+ 4 /*str length*/ + dlen);
+ s << (Q_UINT8)SSH2_FXP_SYMLINK;
+ s << (Q_UINT32)id;
+ s.writeBytes(target.data(), tlen);
+ s.writeBytes(destPath.data(), dlen);
+
+ putPacket(p);
+ getPacket(p);
+
+ QDataStream r(p, IO_ReadOnly);
+ Q_UINT8 type;
+
+ r >> type >> id;
+ if( id != expectedId ) {
+ kdError(KIO_SFTP_DB) << "sftpSymLink(): sftp packet id mismatch" << endl;
+ return -1;
+ }
+
+ if( type != SSH2_FXP_STATUS ) {
+ kdError(KIO_SFTP_DB) << "sftpSymLink(): unexpected message type of " << type << endl;
+ return -1;
+ }
+
+ Q_UINT32 code;
+ r >> code;
+ if( code != SSH2_FX_OK ) {
+ kdError(KIO_SFTP_DB) << "sftpSymLink(): rename failed with err code " << code << endl;
+ }
+
+ return code;
+}
+
+/** Stats a file. */
+int sftpProtocol::sftpStat(const KURL& url, sftpFileAttr& attr) {
+
+ kdDebug(KIO_SFTP_DB) << "sftpStat(): " << url << endl;
+
+ QCString path = remoteEncoding()->encode(url.path());
+ uint len = path.length();
+
+ Q_UINT32 id, expectedId;
+ id = expectedId = mMsgId++;
+
+ QByteArray p;
+ QDataStream s(p, IO_WriteOnly);
+ s << (Q_UINT32)(1 /*type*/ + 4 /*id*/ + 4 /*str length*/ + len);
+ s << (Q_UINT8)SSH2_FXP_LSTAT;
+ s << (Q_UINT32)id;
+ s.writeBytes(path.data(), len);
+
+ putPacket(p);
+ getPacket(p);
+
+ QDataStream r(p, IO_ReadOnly);
+ Q_UINT8 type;
+
+ r >> type >> id;
+ if( id != expectedId ) {
+ kdError(KIO_SFTP_DB) << "sftpStat(): sftp packet id mismatch" << endl;
+ return -1;
+ }
+
+ if( type == SSH2_FXP_STATUS ) {
+ Q_UINT32 errCode;
+ r >> errCode;
+ kdError(KIO_SFTP_DB) << "sftpStat(): stat failed with code " << errCode << endl;
+ return errCode;
+ }
+
+ if( type != SSH2_FXP_ATTRS ) {
+ kdError(KIO_SFTP_DB) << "sftpStat(): unexpected message type of " << type << endl;
+ return -1;
+ }
+
+ r >> attr;
+ attr.setFilename(url.fileName());
+ kdDebug(KIO_SFTP_DB) << "sftpStat(): " << attr << endl;
+
+ // If the stat'ed resource is a symlink, perform a recursive stat
+ // to determine the actual destination's type (file/dir).
+ if( S_ISLNK(attr.permissions()) && isSupportedOperation(SSH2_FXP_READLINK) ) {
+
+ QString target;
+ int code = sftpReadLink( url, target );
+
+ if ( code != SSH2_FX_OK ) {
+ kdError(KIO_SFTP_DB) << "sftpStat(): Unable to stat symlink destination" << endl;
+ return -1;
+ }
+
+ kdDebug(KIO_SFTP_DB) << "sftpStat(): Resource is a symlink to -> " << target << endl;
+
+ KURL dest( url );
+ if( target[0] == '/' )
+ dest.setPath(target);
+ else
+ dest.setFileName(target);
+
+ dest.cleanPath();
+
+ // Ignore symlinks that point to themselves...
+ if ( dest != url ) {
+
+ sftpFileAttr attr2 (remoteEncoding());
+ (void) sftpStat(dest, attr2);
+
+ if (attr2.linkType() == 0)
+ attr.setLinkType(attr2.fileType());
+ else
+ attr.setLinkType(attr2.linkType());
+
+ attr.setLinkDestination(target);
+
+ kdDebug(KIO_SFTP_DB) << "sftpStat(): File type: " << attr.fileType() << endl;
+ }
+ }
+
+ return SSH2_FX_OK;
+}
+
+
+int sftpProtocol::sftpOpen(const KURL& url, const Q_UINT32 pflags,
+ const sftpFileAttr& attr, QByteArray& handle) {
+ kdDebug(KIO_SFTP_DB) << "sftpOpen(" << url << ", handle" << endl;
+
+ QCString path = remoteEncoding()->encode(url.path());
+ uint len = path.length();
+
+ Q_UINT32 id, expectedId;
+ id = expectedId = mMsgId++;
+
+ QByteArray p;
+ QDataStream s(p, IO_WriteOnly);
+ s << (Q_UINT32)(1 /*type*/ + 4 /*id*/ +
+ 4 /*str length*/ + len +
+ 4 /*pflags*/ + attr.size());
+ s << (Q_UINT8)SSH2_FXP_OPEN;
+ s << (Q_UINT32)id;
+ s.writeBytes(path.data(), len);
+ s << pflags;
+ s << attr;
+
+ putPacket(p);
+ getPacket(p);
+
+ QDataStream r(p, IO_ReadOnly);
+ Q_UINT8 type;
+
+ r >> type >> id;
+ if( id != expectedId ) {
+ kdError(KIO_SFTP_DB) << "sftpOpen(): sftp packet id mismatch" << endl;
+ return -1;
+ }
+
+ if( type == SSH2_FXP_STATUS ) {
+ Q_UINT32 errCode;
+ r >> errCode;
+ return errCode;
+ }
+
+ if( type != SSH2_FXP_HANDLE ) {
+ kdError(KIO_SFTP_DB) << "sftpOpen(): unexpected message type of " << type << endl;
+ return -1;
+ }
+
+ r >> handle;
+ if( handle.size() > 256 ) {
+ kdError(KIO_SFTP_DB) << "sftpOpen(): handle exceeds max length" << endl;
+ return -1;
+ }
+
+ kdDebug(KIO_SFTP_DB) << "sftpOpen(): handle (" << handle.size() << "): [" << handle << "]" << endl;
+ return SSH2_FX_OK;
+}
+
+
+int sftpProtocol::sftpRead(const QByteArray& handle, KIO::filesize_t offset, Q_UINT32 len, QByteArray& data)
+{
+ // kdDebug(KIO_SFTP_DB) << "sftpRead( offset = " << offset << ", len = " << len << ")" << endl;
+ QByteArray p;
+ QDataStream s(p, IO_WriteOnly);
+
+ Q_UINT32 id, expectedId;
+ id = expectedId = mMsgId++;
+ s << (Q_UINT32)(1 /*type*/ + 4 /*id*/ +
+ 4 /*str length*/ + handle.size() +
+ 8 /*offset*/ + 4 /*length*/);
+ s << (Q_UINT8)SSH2_FXP_READ;
+ s << (Q_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);
+
+ QDataStream r(p, IO_ReadOnly);
+ Q_UINT8 type;
+
+ r >> type >> id;
+ if( id != expectedId ) {
+ kdError(KIO_SFTP_DB) << "sftpRead: sftp packet id mismatch" << endl;
+ return -1;
+ }
+
+ if( type == SSH2_FXP_STATUS ) {
+ Q_UINT32 errCode;
+ r >> errCode;
+ kdError(KIO_SFTP_DB) << "sftpRead: read failed with code " << errCode << endl;
+ return errCode;
+ }
+
+ if( type != SSH2_FXP_DATA ) {
+ kdError(KIO_SFTP_DB) << "sftpRead: unexpected message type of " << type << endl;
+ return -1;
+ }
+
+ r >> data;
+
+ return SSH2_FX_OK;
+}
+
+
+int sftpProtocol::sftpWrite(const QByteArray& handle, KIO::filesize_t offset, const QByteArray& data){
+// kdDebug(KIO_SFTP_DB) << "sftpWrite( offset = " << offset <<
+// ", data sz = " << data.size() << ")" << endl;
+ QByteArray p;
+ QDataStream s(p, IO_WriteOnly);
+
+ Q_UINT32 id, expectedId;
+ id = expectedId = mMsgId++;
+ s << (Q_UINT32)(1 /*type*/ + 4 /*id*/ +
+ 4 /*str length*/ + handle.size() +
+ 8 /*offset*/ +
+ 4 /* data size */ + data.size());
+ s << (Q_UINT8)SSH2_FXP_WRITE;
+ s << (Q_UINT32)id;
+ s << handle;
+ s << offset; // we don't have a convienient 64 bit int so set upper int to zero
+ s << data;
+
+// kdDebug(KIO_SFTP_DB) << "sftpWrite(): SSH2_FXP_WRITE, id:"
+// << id << ", handle:" << handle << ", offset:" << offset << ", some data" << endl;
+
+// kdDebug(KIO_SFTP_DB) << "sftpWrite(): send packet [" << p << "]" << endl;
+
+ putPacket(p);
+ getPacket(p);
+
+// kdDebug(KIO_SFTP_DB) << "sftpWrite(): received packet [" << p << "]" << endl;
+
+ QDataStream r(p, IO_ReadOnly);
+ Q_UINT8 type;
+
+ r >> type >> id;
+ if( id != expectedId ) {
+ kdError(KIO_SFTP_DB) << "sftpWrite(): sftp packet id mismatch, got "
+ << id << ", expected " << expectedId << endl;
+ return -1;
+ }
+
+ if( type != SSH2_FXP_STATUS ) {
+ kdError(KIO_SFTP_DB) << "sftpWrite(): unexpected message type of " << type << endl;
+ return -1;
+ }
+
+ Q_UINT32 code;
+ r >> code;
+ return code;
+}
+
+
diff --git a/kioslave/sftp/kio_sftp.h b/kioslave/sftp/kio_sftp.h
new file mode 100644
index 000000000..ff99b4760
--- /dev/null
+++ b/kioslave/sftp/kio_sftp.h
@@ -0,0 +1,149 @@
+/***************************************************************************
+ 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. *
+ * *
+ ***************************************************************************/
+#ifndef __kio_sftp_h__
+#define __kio_sftp_h__
+
+#include <qstring.h>
+#include <qcstring.h>
+#include <qobject.h>
+
+#include <kurl.h>
+#include <kio/global.h>
+#include <kio/slavebase.h>
+#include <kdebug.h>
+
+#include "process.h"
+#include "sftpfileattr.h"
+#include "ksshprocess.h"
+
+#define KIO_SFTP_DB 7120
+
+
+class sftpProtocol : public KIO::SlaveBase
+{
+
+public:
+ sftpProtocol(const QCString &pool_socket, const QCString &app_socket);
+ virtual ~sftpProtocol();
+ virtual void setHost(const QString& h, int port, const QString& user, const QString& pass);
+ virtual void get(const KURL& url);
+ virtual void listDir(const KURL& url) ;
+ virtual void mimetype(const KURL& url);
+ virtual void stat(const KURL& url);
+ virtual void copy(const KURL &src, const KURL &dest, int permissions, 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 QString& 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();
+
+private: // Private variables
+ /** True if ioslave is connected to sftp server. */
+ bool mConnected;
+
+ /** Host we are connected to. */
+ QString mHost;
+
+ /** Port we are connected to. */
+ int mPort;
+
+ /** Ssh process to which we send the sftp packets. */
+ KSshProcess ssh;
+
+ /** Username to use when connecting */
+ QString mUsername;
+
+ /** User's password */
+ QString mPassword;
+
+ /** Message id of the last sftp packet we sent. */
+ unsigned int mMsgId;
+
+ /** Type of packet we are expecting to receive next. */
+ unsigned char mExpected;
+
+ /** Version of the sftp protocol we are using. */
+ int sftpVersion;
+
+ struct Status
+ {
+ int code;
+ KIO::filesize_t size;
+ QString text;
+ };
+
+private: // private methods
+ bool getPacket(QByteArray& msg);
+
+ /* Type is a sftp packet type found in .sftp.h'.
+ * Example: SSH2_FXP_READLINK, SSH2_FXP_RENAME, etc.
+ *
+ * Returns true if the type is supported by the sftp protocol
+ * version negotiated by the client and server (sftpVersion).
+ */
+ bool isSupportedOperation(int type);
+ /** Used to have the server canonicalize any given path name to an absolute path.
+ This is useful for converting path names containing ".." components or relative
+ pathnames without a leading slash into absolute paths.
+ Returns the canonicalized url. */
+ int sftpRealPath(const KURL& url, KURL& newUrl);
+
+ /** Send an sftp packet to stdin of the ssh process. */
+ bool putPacket(QByteArray& p);
+ /** Process SSH_FXP_STATUS packets. */
+ void processStatus(Q_UINT8, const QString& message = QString::null);
+ /** Process SSH_FXP_STATUS packes and return the result. */
+ Status doProcessStatus(Q_UINT8, const QString& message = QString::null);
+ /** Opens a directory handle for url.path. Returns true if succeeds. */
+ int sftpOpenDirectory(const KURL& url, QByteArray& handle);
+ /** Closes a directory or file handle. */
+ int sftpClose(const QByteArray& handle);
+ /** Send a sftp command to rename a file or 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 QString& target, const KURL& dest);
+ /** Get directory listings. */
+ int sftpReadDir(const QByteArray& handle, const KURL& url);
+ /** Retrieves the destination of a link. */
+ int sftpReadLink(const KURL& url, QString& target);
+ /** Stats a file. */
+ int sftpStat(const KURL& url, sftpFileAttr& attr);
+ /** No descriptions */
+ int sftpOpen(const KURL& url, const Q_UINT32 pflags, const sftpFileAttr& attr, QByteArray& handle);
+ /** No descriptions */
+ int sftpRead(const QByteArray& handle, KIO::filesize_t offset, Q_UINT32 len, QByteArray& data);
+ /** No descriptions */
+ int sftpWrite(const QByteArray& handle, KIO::filesize_t offset, const QByteArray& 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, KIO::filesize_t offset = 0, int fd = -1);
+ void sftpPut( const KURL& dest, int permissions, bool resume, bool overwrite, int fd = -1);
+};
+#endif
diff --git a/kioslave/sftp/ksshprocess.cpp b/kioslave/sftp/ksshprocess.cpp
new file mode 100644
index 000000000..c0393445d
--- /dev/null
+++ b/kioslave/sftp/ksshprocess.cpp
@@ -0,0 +1,1104 @@
+/***************************************************************************
+ 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 kdelibs 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 <config.h>
+
+#include "ksshprocess.h"
+
+#include <stdio.h>
+#include <errno.h>
+
+#ifdef HAVE_SYS_TIME_H
+#include <sys/time.h>
+#endif
+
+#include <kstandarddirs.h>
+#include <klocale.h>
+#include <qregexp.h>
+
+/*
+ * 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.
+ */
+QRegExp KSshProcess::versionStrs[] = {
+ QRegExp("OpenSSH_3\\.[6-9]|OpenSSH_[1-9]*[4-9]\\.[0-9]"),
+ QRegExp("OpenSSH"),
+ QRegExp("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"
+};
+
+QRegExp KSshProcess::hostKeyMissingMsg[] = {
+ QRegExp("The authenticity of host|No (DSA|RSA) host key is known for"),
+ QRegExp("The authenticity of host|No (DSA|RSA) host key is known for"),
+ QRegExp("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!"
+};
+
+QRegExp KSshProcess::keyFingerprintMsg[] = {
+ QRegExp("..(:..){15}"),
+ QRegExp("..(:..){15}"),
+ QRegExp(".....(-.....){10}")
+};
+
+QRegExp KSshProcess::knownHostsFileMsg[] = {
+ QRegExp("Add correct host key in (.*) to get rid of this message."),
+ QRegExp("Add correct host key in (.*) to get rid of this message."),
+ QRegExp("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".
+QRegExp KSshProcess::hostKeyVerifyFailedMsg[] = {
+ QRegExp("Host key verification failed\\."),
+ QRegExp("Host key verification failed\\."),
+ QRegExp("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 = KStandardDirs::findExe(QString::fromLatin1("ssh"));
+ kdDebug(KSSHPROC) << "KSshProcess::KSshProcess(): ssh path [" <<
+ mSshPath << "]" << endl;
+
+ installSignalHandlers();
+}
+
+KSshProcess::KSshProcess(QString 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(QString pathToSsh) {
+ mSshPath = pathToSsh;
+ version();
+ if( mVersion == UNKNOWN_VER )
+ return false;
+
+ return true;
+}
+
+KSshProcess::SshVersion KSshProcess::version() {
+ QString 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';
+ QString 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;
+}
+/*
+QString KSshProcess::versionStr() {
+ if( mVersion == UNKNOWN_VER ) {
+ version();
+ if( mVersion == UNKNOWN_VER )
+ return QString::null;
+ }
+
+ return QString::fromLatin1(versionStrs[mVersion]);
+}
+*/
+
+bool KSshProcess::setOptions(const SshOptList& opts) {
+ kdDebug(KSSHPROC) << "KSshProcess::setOptions()" << endl;
+ mArgs.clear();
+ SshOptListConstIterator it;
+ QString cmd, subsystem;
+ mPassword = mUsername = mHost = QString::null;
+ QCString 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 += QString::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() {
+ QValueListIterator<QCString> it;
+ for( it = mArgs.begin(); it != mArgs.end(); ++it) {
+ kdDebug(KSSHPROC) << "arg: " << *it << endl;
+ }
+}
+
+
+int KSshProcess::error(QString& 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(QString password) {
+ kdDebug(KSSHPROC) << "KSshProcess::setPassword(password:xxxxxxxx)" << endl;
+ mPassword = password;
+}
+
+QString KSshProcess::getLine() {
+ static QStringList buffer;
+ QString line = QString::null;
+ QCString 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(QString(ptyLine));
+ }
+
+ if( ! errLine.isEmpty() ) {
+ buffer.prepend(QString(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.
+ if( buffer.empty() ) {
+ //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 QString::null;
+ }
+ else if( ret == -1 ) {
+ kdDebug(KSSHPROC) << "KSshProcess::connect(): "
+ << "select error: " << strerror(errno) << endl;
+ mError = ERR_INTERNAL;
+ return QString::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);
+ buffer.prepend(QString(ptyLine));
+ //kdDebug(KSSHPROC) << "KSshProcess::getLine(): "
+ // "line from pty -" << ptyLine << endl;
+ }
+
+ if( FD_ISSET(errfd, &rfds) ) {
+ errLine = ssh.readLineFromStderr(false);
+ buffer.prepend(QString(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;
+ }
+
+ }
+ }
+
+ // 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;
+
+ QString line; // a line from ssh
+ QString 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 = QString::null;
+ mKnownHostsFile = QString::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(QString::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(QString)
+ // 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(QChar('X'));
+
+ // Set the password to null so we will request another
+ // password if this one fails.
+ mPassword = QString::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(QString)
+ // 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(QChar('X'));
+
+ // Set the password to null so we will request another
+ // password if this one fails.
+ mPassword = QString::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/kioslave/sftp/ksshprocess.h b/kioslave/sftp/ksshprocess.h
new file mode 100644
index 000000000..2ec1abfd6
--- /dev/null
+++ b/kioslave/sftp/ksshprocess.h
@@ -0,0 +1,623 @@
+/***************************************************************************
+ 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 <sys/types.h>
+#include <sys/wait.h>
+#include <signal.h>
+#include <unistd.h>
+
+#include <qvaluelist.h>
+
+#include <kdebug.h>
+
+#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;
+ * QString 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:
+ Q_UINT32 opt;
+ QString str;
+ Q_INT32 num;
+ bool boolean;
+ };
+
+ /**
+ * List of SshOptions and associated iterators
+ */
+ typedef QValueList<SshOpt> SshOptList;
+ typedef QValueListIterator<SshOpt> SshOptListIterator;
+ typedef QValueListConstIterator<SshOpt> 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(QString 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(QString 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
+ */
+ //QString 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(QString& msg);
+
+ /**
+ * Get the last error encountered by KSshProcess.
+ * @return The error number. See SshError for descriptions.
+ */
+ int error() { return mError; }
+
+ QString 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:
+ * <ul>
+ * <li>ERR_NO_OPTIONS</li>
+ * <li>ERR_CANNOT_LAUNCH</li>
+ * <li>ERR_INVALID_STATE</li>
+ * <li>ERR_NEED_PASSWD</li>
+ * <li>ERR_AUTH_FAILED</li>
+ * <li>ERR_NEW_HOST_KEY</li>
+ * <li>ERR_KEY_ACCEPTED</li>
+ * <li>ERR_DIFF_HOST_KEY</li>
+ * <li>ERR_INTERNAL</li>
+ * <li>ERR_INTERACT</li>
+ * </ul>
+ *
+ * @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(QString 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.
+ */
+ QString 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.
+ */
+ QString mPassword;
+
+ /**
+ * User's username.
+ */
+ QString mUsername;
+
+ /**
+ * Name of host we are connecting to.
+ */
+ QString 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.
+ */
+ QString mKeyFingerprint;
+
+ /**
+ * The location of the known host key file. We grab this from
+ * any error messages ssh prints out.
+ */
+ QString 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.
+ */
+ QString 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();
+
+ QString getLine();
+
+ static QRegExp versionStrs[];
+ static const char * const passwordPrompt[];
+ static const char * const passphrasePrompt[];
+ static const char * const authSuccessMsg[];
+ static const char * const authFailedMsg[];
+ static QRegExp hostKeyMissingMsg[];
+ static const char * const hostKeyChangedMsg[];
+ static const char * const continuePrompt[];
+ static const char * const hostKeyAcceptedMsg[];
+ static const char * const tryAgainMsg[];
+ static QRegExp hostKeyVerifyFailedMsg[];
+ static const char * const connectionClosedMsg[];
+ static const char * const changeHostKeyOnDiskPrompt[];
+ static QRegExp keyFingerprintMsg[];
+ static QRegExp knownHostsFileMsg[];
+};
+#endif
diff --git a/kioslave/sftp/ksshprocesstest.cpp b/kioslave/sftp/ksshprocesstest.cpp
new file mode 100644
index 000000000..3a37be02c
--- /dev/null
+++ b/kioslave/sftp/ksshprocesstest.cpp
@@ -0,0 +1,98 @@
+#include "ksshprocess.h"
+#include <iostream>
+
+using namespace std;
+
+int main(int argc, char *argv[]) {
+
+ if( argc < 5 ) {
+ cout << "Usage: " << argv[0] <<
+ " <ssh path> <host> <username> <password>" << 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 = QString(argv[2]);
+ opts.append(opt);
+
+ opt.opt = KSshProcess::SSH_USERNAME;
+ opt.str = QString(argv[3]);
+ opts.append(opt);
+
+// opt.opt = KSshProcess::SSH_PASSWD;
+// opt.str = QString(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(QString(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/kioslave/sftp/process.cpp b/kioslave/sftp/process.cpp
new file mode 100644
index 000000000..fcf012514
--- /dev/null
+++ b/kioslave/sftp/process.cpp
@@ -0,0 +1,493 @@
+/* vi: ts=8 sts=4 sw=4
+ *
+ *
+ * This file is part of the KDE project, module kdesu.
+ * Copyright (C) 1999,2000 Geert Jansen <jansen@kde.org>
+ *
+ * This file contains code from TEShell.C of the KDE konsole.
+ * Copyright (c) 1997,1998 by Lars Doelle <lars.doelle@on-line.de>
+ *
+ * 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 <config.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <signal.h>
+#include <errno.h>
+#include <string.h>
+#include <termios.h>
+#include <signal.h>
+
+#include <sys/types.h>
+#include <sys/wait.h>
+#include <sys/stat.h>
+#include <sys/time.h>
+#include <sys/resource.h>
+#include <sys/socket.h>
+
+#if defined(__SVR4) && defined(sun)
+#include <stropts.h>
+#include <sys/stream.h>
+#endif
+
+#ifdef HAVE_SYS_SELECT_H
+#include <sys/select.h> // Needed on some systems.
+#endif
+
+#include <qglobal.h>
+#include <qcstring.h>
+#include <qfile.h>
+
+#include <kdebug.h>
+#include <kstandarddirs.h>
+
+#include "process.h"
+#include <kdesu/kdesu_pty.h>
+#include <kdesu/kcookie.h>
+
+
+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.
+ */
+
+
+QCString MyPtyProcess::readLineFrom(int fd, QCString& inbuf, bool block)
+{
+ int pos;
+ QCString 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(QCString line, bool addnl)
+{
+ if (!line.isEmpty())
+ write(m_Fd, line, line.length());
+ if (addnl)
+ write(m_Fd, "\n", 1);
+}
+
+void MyPtyProcess::unreadLineFrom(QCString inbuf, QCString 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(QCString 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.
+ QCString path;
+ if (command.contains('/'))
+ path = command;
+ else
+ {
+ QString file = KStandardDirs::findExe(command);
+ if (file.isEmpty())
+ {
+ kdError(PTYPROC) << k_lineinfo << command << " not found\n";
+ _exit(1);
+ }
+ path = QFile::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)
+ {
+ QCString line = readLine(false);
+ while (!line.isNull())
+ {
+ if (!m_Exit.isEmpty() && !qstrnicmp(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/kioslave/sftp/process.h b/kioslave/sftp/process.h
new file mode 100644
index 000000000..64dcfb973
--- /dev/null
+++ b/kioslave/sftp/process.h
@@ -0,0 +1,148 @@
+/* vi: ts=8 sts=4 sw=4
+ *
+ *
+ * This file is part of the KDE project, module kdesu.
+ * Copyright (C) 1999,2000 Geert Jansen <jansen@kde.org>
+ *
+ * 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 <qcstring.h>
+#include <qstring.h>
+#include <qstringlist.h>
+#include <qvaluelist.h>
+
+#define PTYPROC 7120
+
+class PTY;
+typedef QValueList<QCString> 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(QCString 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.
+ */
+ QCString readLine(bool block = true)
+ { return readLineFrom(m_Fd, m_ptyBuf, block); }
+
+ QCString readLineFromPty(bool block = true)
+ { return readLineFrom(m_Fd, m_ptyBuf, block); }
+
+ QCString readLineFromStdout(bool block = true)
+ { return readLineFrom(m_stdinout, m_stdoutBuf, block); }
+
+ QCString 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(QCString 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(QCString line, bool addNewline = true)
+ { unreadLineFrom(m_ptyBuf, line, addNewline); }
+
+ void unreadLineFromPty(QCString line, bool addNewline = true)
+ { unreadLineFrom(m_ptyBuf, line, addNewline); }
+
+ void unreadLineFromStderr(QCString line, bool addNewline = true)
+ { unreadLineFrom(m_stderrBuf, line, addNewline); }
+
+ void unreadLineFromStdout(QCString 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(QCString 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;
+ QCString m_Command, m_Exit;
+
+private:
+ int init();
+ int SetupTTY(int fd);
+
+ PTY *m_pPTY;
+ QCString m_TTY;
+ QCString m_ptyBuf, m_stderrBuf, m_stdoutBuf;
+
+ QCString readLineFrom(int fd, QCString& inbuf, bool block);
+ void unreadLineFrom(QCString inbuf, QCString line, bool addnl);
+ class PtyProcessPrivate;
+ PtyProcessPrivate *d;
+};
+
+#endif
diff --git a/kioslave/sftp/sftp.h b/kioslave/sftp/sftp.h
new file mode 100644
index 000000000..95518130d
--- /dev/null
+++ b/kioslave/sftp/sftp.h
@@ -0,0 +1,91 @@
+/* $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/kioslave/sftp/sftp.protocol b/kioslave/sftp/sftp.protocol
new file mode 100644
index 000000000..177cd86e9
--- /dev/null
+++ b/kioslave/sftp/sftp.protocol
@@ -0,0 +1,84 @@
+[Protocol]
+exec=kio_sftp
+protocol=sftp
+input=none
+listing=Name,Type,Size,Date,Access,Owner,Group,Link
+output=filesystem
+copyToFile=true
+copyFromFile=true
+reading=true
+writing=true
+makedir=true
+deleting=true
+moving=true
+Icon=ftp
+Description=A kioslave for sftp
+Description[af]='n Kioslave vir sftp
+Description[be]=Kioslave для sftp
+Description[bg]=kioslave за sftp
+Description[bn]=এস.এফ.টি.পি-র জন্য একটি kioslave
+Description[br]=Ur kioslave evit sftp
+Description[bs]=kioslave za SFTP
+Description[ca]=Un kioslave per a sftp
+Description[cs]=Protokol KDE pro sftp
+Description[csb]=Plugins dlô procedurë òbsłużënkù sftp
+Description[da]=En kioslave for sftp
+Description[de]=Ein-/Ausgabemodul für das sftp-Protokoll
+Description[el]=Ένας kioslave για sftp
+Description[eo]=kenelsklavo por sftp
+Description[es]=Un kioslave para sftp
+Description[et]=SFTP IO-moodul
+Description[eu]=Sftp-rako kioslave bat
+Description[fa]=یک kioslave برای sftp
+Description[fi]=Sftp:n liitin
+Description[fr]=Un module d'entrées / sorties pour le sftp
+Description[fy]=Een kioslave Foar sftp
+Description[ga]=kioslave le haghaidh sftp
+Description[gl]=Un kioslave para sftp
+Description[he]=ממשק kioslave עבור sftp
+Description[hi]=एसएफटीपी के लिए केआईओस्लेव
+Description[hr]=Kioslave za SFTP
+Description[hu]=KDE-protokoll az sftp-hez
+Description[is]=kioslave fyrir sftp
+Description[it]=Un kioslave per sftp
+Description[ja]=sftp のための kioslave
+Description[kk]=sftp-ке арналған енгізу-шығару модулі
+Description[km]=kioslave របស់ sftp
+Description[ko]=SFTP KIO 슬레이브
+Description[lt]=Kiovergas sftp protokolui
+Description[lv]=KIO vergs priekš sftp
+Description[mk]=kio-служител за sftp
+Description[ms]=Kioslave untuk sftp
+Description[nb]=En kioslave for sftp
+Description[nds]=En In-/Utgaavdeenst för sftpl
+Description[ne]=sftp का लागि एउटा किओस्लेभ
+Description[nl]=Een kioslave voor sftp
+Description[nn]=Ein kioslave for sftp
+Description[pa]=sftp ਲਈ kioslave
+Description[pl]=Wtyczka protokołu sftp
+Description[pt]=Um 'kioslave' para sftp
+Description[pt_BR]=Uma implementação para o sftp
+Description[ro]=Un dispozitiv de I/E pentru SFTP
+Description[ru]=Модуль ввода-вывода для sftp
+Description[rw]=kioslave ya sftp
+Description[se]=ŠO-šláva sftp-protokolla várás
+Description[sk]=kioslave pre sftp
+Description[sl]=kioslave za sftp
+Description[sr]=Kioslave за SFTP
+Description[sr@Latn]=Kioslave za SFTP
+Description[sv]=En I/O-slav för SFTP
+Description[ta]=sftpக்கான ஒரு க்யோஸ்லேவ்
+Description[te]=ఎస్ ఎఫ్ టి పి కొరకు ఐఒ బానిస
+Description[th]=ตัวนำข้อมูลเข้า-ออกสำหรับ sftp
+Description[tr]=Sftp için kioslave
+Description[tt]=sftp öçen kioslave
+Description[uk]=Підлеглий В/В для sftp
+Description[uz]=SFTP uchun KCH-sleyv
+Description[uz@cyrillic]=SFTP учун КЧ-слейв
+Description[vi]=A kioslave (đày tớ vào ra KDE) cho sftp
+Description[wa]=On kioslave po sftp
+Description[zh_CN]=sftp 的 kioslave
+Description[zh_TW]=sftp 的 kioslave
+DocPath=kioslave/sftp.html
+Icon=ftp
+Class=:internet
diff --git a/kioslave/sftp/sftpfileattr.cpp b/kioslave/sftp/sftpfileattr.cpp
new file mode 100644
index 000000000..07aada36d
--- /dev/null
+++ b/kioslave/sftp/sftpfileattr.cpp
@@ -0,0 +1,346 @@
+/***************************************************************************
+ 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 <ctype.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+
+#include <qstring.h>
+#include <qdatastream.h>
+
+#include <kio/global.h>
+#include <kremoteencoding.h>
+
+using namespace KIO;
+
+sftpFileAttr::sftpFileAttr(){
+ clear();
+ mDirAttrs = false;
+}
+
+sftpFileAttr::sftpFileAttr(KRemoteEncoding* encoding){
+ clear();
+ mDirAttrs = false;
+ mEncoding = encoding;
+}
+
+
+/** Constructor to initialize the file attributes on declaration. */
+sftpFileAttr::sftpFileAttr(Q_ULLONG size, uid_t uid, gid_t gid,
+ mode_t permissions, time_t atime,
+ time_t mtime, Q_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 */
+QDataStream& operator<< (QDataStream& s, const sftpFileAttr& fa) {
+ s << (Q_UINT32)fa.mFlags;
+
+ if( fa.mFlags & SSH2_FILEXFER_ATTR_SIZE )
+ { s << (Q_ULLONG)fa.mSize; }
+
+ if( fa.mFlags & SSH2_FILEXFER_ATTR_UIDGID )
+ { s << (Q_UINT32)fa.mUid << (Q_UINT32)fa.mGid; }
+
+ if( fa.mFlags & SSH2_FILEXFER_ATTR_PERMISSIONS )
+ { s << (Q_UINT32)fa.mPermissions; }
+
+ if( fa.mFlags & SSH2_FILEXFER_ATTR_ACMODTIME )
+ { s << (Q_UINT32)fa.mAtime << (Q_UINT32)fa.mMtime; }
+
+ if( fa.mFlags & SSH2_FILEXFER_ATTR_EXTENDED ) {
+ s << (Q_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 */
+QDataStream& operator>> (QDataStream& s, sftpFileAttr& fa) {
+
+ // XXX Add some error checking in here in case
+ // we get a bad sftp packet.
+
+ fa.clear();
+
+ if( fa.mDirAttrs ) {
+ QCString 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 ) {
+ Q_ULLONG fileSize;
+ s >> fileSize;
+ fa.setFileSize(fileSize);
+ }
+
+ Q_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.
+ QString 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();
+
+ QString 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 = QString::null;
+ mGroupName = QString::null;
+ mUserName = QString::null;
+ mLinkDestination = QString::null;
+ mFlags = 0;
+ mLongname = "\0";
+ mLinkType = 0;
+}
+
+/** Return the size of the sftp attribute. */
+Q_UINT32 sftpFileAttr::size() const{
+ Q_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;
+}
+// vim:ts=4:sw=4
diff --git a/kioslave/sftp/sftpfileattr.h b/kioslave/sftp/sftpfileattr.h
new file mode 100644
index 000000000..28743504b
--- /dev/null
+++ b/kioslave/sftp/sftpfileattr.h
@@ -0,0 +1,261 @@
+/***************************************************************************
+ 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 <sys/types.h>
+
+#include <qglobal.h>
+#include <qstring.h>
+#include <qdatastream.h>
+
+#include <kio/global.h>
+#include <kdebug.h>
+
+#include "sftp.h"
+
+/**
+ *@author Lucas Fisher
+ */
+
+class KRemoteEncoding;
+
+class sftpFileAttr {
+
+private: // Private attributes
+ /** Name of file. */
+ QString mFilename;
+
+ /** Specifies which fields of the file attribute are available. */
+ Q_UINT32 mFlags;
+
+ /** Size of the file in bytes. Should be 64 bit safe. */
+ Q_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 */
+ Q_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. */
+ QCString mLongname;
+
+ QString mUserName;
+ QString mGroupName;
+
+ /** If file is a link, contains the destination of the link */
+ QString 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(Q_ULLONG size_, uid_t uid_, gid_t gid_, mode_t permissions_,
+ time_t atime_, time_t mtime_, Q_UINT32 extendedCount_ = 0);
+
+ /** Return the size of the sftp attribute not including filename or longname*/
+ Q_UINT32 size() const;
+
+ /** Clear all attributes and flags. */
+ void clear();
+
+ /** Set the size of the file. */
+ void setFileSize(Q_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. */
+ Q_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(QString 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'. */
+ QString longname() const { return mLongname; }
+
+ void setLinkDestination(const QString& target)
+ { mLinkDestination = target; }
+
+ QString 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 QString& fn)
+ { mFilename = fn; }
+
+ QString filename() const
+ { return mFilename; }
+
+ /** Returns a UDSEntry describing the file.
+ The UDSEntry is generated from the sftp file attributes. */
+ KIO::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 QDataStream& operator<< (QDataStream&, 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 QDataStream& operator>> (QDataStream&, 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