/***************************************************************************
                          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;
}


