/*************************************************************************** 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 #endif #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #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 TQCString &pool_socket, const TQCString &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, TQString::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; TQCString dest_orig ( TQFile::encodeName(dest.path()) ); bool origExists = (KDE_lstat( dest_orig.data(), &buff_orig ) != -1); if (origExists) { if (S_ISDIR(buff_orig.st_mode)) { error(ERR_IS_DIRECTORY, dest.prettyURL()); return; } if (!overwrite) { error(ERR_FILE_ALREADY_EXIST, dest.prettyURL()); return; } } KIO::filesize_t offset = 0; TQCString dest_part ( dest_orig + ".part" ); int fd = -1; bool partExists = false; bool markPartial = config()->readBoolEntry("MarkPartial", true); if (markPartial) { KDE_struct_stat buff_part; partExists = (KDE_stat( dest_part.data(), &buff_part ) != -1); if (partExists && buff_part.st_size > 0 && S_ISREG(buff_part.st_mode)) { if (canResume( buff_part.st_size )) { offset = buff_part.st_size; kdDebug(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(TQByteArray()); 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(); TQ_UINT32 pflags = SSH2_FXF_READ; attr.clear(); TQByteArray handle; if( (code = sftpOpen(src, pflags, attr, handle)) != SSH2_FX_OK ) { res.text = src.prettyURL(); res.code = ERR_CANNOT_OPEN_FOR_READING; return res; } // needed for determining mimetype // note: have to emit mimetype before emitting totalsize. TQByteArray buff; TQByteArray mimeBuffer; unsigned int oldSize; bool foundMimetype = false; // How big should each data packet be? Definitely not bigger than 64kb or // you will overflow the 2 byte size variable in a sftp packet. TQ_UINT32 len = 60*1024; code = SSH2_FX_OK; kdDebug(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 TQ_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(TQByteArray()); kdDebug(KIO_SFTP_DB) << "get(): emit finished()" << endl; finished(); } void sftpProtocol::setHost (const TQString& h, int port, const TQString& user, const TQString& 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 %1:%2").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 + ":" + TQString::number(mPort); info.commentLabel = i18n("site:"); info.username = mUsername; info.keepPassword = true; /////////////////////////////////////////////////////////////////////////// // Check for cached authentication info if a username AND password were // not specified in setHost(). if( mUsername.isEmpty() && mPassword.isEmpty() ) { kdDebug(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 TQString msg; // msg for dialog box TQString 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, TQString::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, TQString::null); return; } ssh.acceptHostKey(true); break; case KSshProcess::ERR_DIFF_HOST_KEY: caption = i18n("Warning: Host's identity changed."); msg = ssh.errorMsg(); if( KMessageBox::Yes != messageBox(WarningYesNo, msg, caption) ) { closeConnection(); error(ERR_USER_CANCELED, TQString::null); return; } ssh.acceptHostKey(true); break; case KSshProcess::ERR_AUTH_FAILED: infoMessage(i18n("Authentication failed.")); error(ERR_COULD_NOT_LOGIN, i18n("Authentication failed.")); return; case KSshProcess::ERR_AUTH_FAILED_NEW_KEY: msg = ssh.errorMsg(); error(ERR_COULD_NOT_LOGIN, msg); return; case KSshProcess::ERR_AUTH_FAILED_DIFF_KEY: msg = ssh.errorMsg(); error(ERR_COULD_NOT_LOGIN, msg); return; case KSshProcess::ERR_CLOSED_BY_REMOTE_HOST: infoMessage(i18n("Connection failed.")); caption = i18n("Connection closed by remote host."); msg = ssh.errorMsg(); messageBox(Information, msg, caption); closeConnection(); error(ERR_COULD_NOT_LOGIN, msg); return; case KSshProcess::ERR_INTERACT: case KSshProcess::ERR_INTERNAL: case KSshProcess::ERR_UNKNOWN: case KSshProcess::ERR_INVALID_STATE: case KSshProcess::ERR_CANNOT_LAUNCH: case KSshProcess::ERR_HOST_KEY_REJECTED: default: infoMessage(i18n("Connection failed.")); caption = i18n("Unexpected SFTP error: %1").arg(err); msg = ssh.errorMsg(); messageBox(Information, msg, caption); closeConnection(); error(ERR_UNKNOWN, msg); return; } } // catch all in case we did something wrong above if( !mConnected ) { error(ERR_INTERNAL, TQString::null); return; } // Now send init packet. kdDebug(KIO_SFTP_DB) << "openConnection(): Sending SSH2_FXP_INIT packet." << endl; TQByteArray p; TQDataStream packet(p, IO_WriteOnly); packet << (TQ_UINT32)5; // packet length packet << (TQ_UINT8) SSH2_FXP_INIT; // packet type packet << (TQ_UINT32)SSH2_FILEXFER_VERSION; // client version putPacket(p); getPacket(p); TQDataStream s(p, IO_ReadOnly); TQ_UINT32 version; TQ_UINT8 type; s >> type; kdDebug(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; TQCString file (TQFile::encodeName(src.path())); if (KDE_lstat(file.data(), &buff) == -1) { error (ERR_DOES_NOT_EXIST, src.prettyURL()); return; } if (S_ISDIR (buff.st_mode)) { error (ERR_IS_DIRECTORY, src.prettyURL()); return; } int fd = KDE_open (file.data(), O_RDONLY); if (fd == -1) { error (ERR_CANNOT_OPEN_FOR_READING, src.prettyURL()); return; } totalSize (buff.st_size); sftpPut (dest, permissions, false, overwrite, fd); // Close the file descriptor... ::close( fd ); } void sftpProtocol::sftpPut( const KURL& dest, int permissions, bool resume, bool overwrite, int fd ) { openConnection(); if( !mConnected ) return; kdDebug(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(): 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" ); TQ_UINT64 offset = 0; bool partExists = false; bool markPartial = config()->readBoolEntry("MarkPartial", true); if( markPartial ) { sftpFileAttr partAttr(remoteEncoding()); code = sftpStat(partUrl, partAttr); if( code == SSH2_FX_OK ) { kdDebug(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); TQ_UINT32 pflags = 0; if( overwrite && !resume ) pflags = SSH2_FXF_WRITE | SSH2_FXF_CREAT | SSH2_FXF_TRUNC; else if( !overwrite && !resume ) pflags = SSH2_FXF_WRITE | SSH2_FXF_CREAT | SSH2_FXF_EXCL; else if( overwrite && resume ) pflags = SSH2_FXF_WRITE | SSH2_FXF_CREAT; else if( !overwrite && resume ) pflags = SSH2_FXF_WRITE | SSH2_FXF_CREAT | SSH2_FXF_APPEND; sftpFileAttr attr(remoteEncoding()); TQByteArray handle; // Set the permissions of the file we write to if it didn't already exist // and the permission info is supplied, i.e it is not -1 if( !partExists && !origExists && permissions != -1) attr.setPermissions(permissions); code = sftpOpen( writeUrl, pflags, attr, handle ); if( code != SSH2_FX_OK ) { // Rename the file back to its original name if a // put fails due to permissions problems... if( markPartial && overwrite ) { (void) sftpRename(partUrl, origUrl); writeUrl = origUrl; } 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; TQByteArray buff; do { if( fd != -1 ) { buff.resize( 16*1024 ); if ( (nbytes = ::read(fd, buff.data(), buff.size())) > -1 ) buff.resize( nbytes ); } else { dataReq(); nbytes = readData( buff ); } if( nbytes >= 0 ) { if( (code = sftpWrite(handle, offset, buff)) != SSH2_FX_OK ) { error(ERR_COULD_NOT_WRITE, dest.prettyURL()); return; } offset += nbytes; processedSize(offset); /* Check if slave was killed. According to slavebase.h we * need to leave the slave methods as soon as possible if * the slave is killed. This allows the slave to be cleaned * up properly. */ if( wasKilled() ) { sftpClose(handle); closeConnection(); error(ERR_UNKNOWN, i18n("An internal error occurred. Please try again.")); return; } } } 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 = TQString::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; TQ_UINT32 pflags = SSH2_FXF_READ; TQByteArray handle, mydata; sftpFileAttr attr(remoteEncoding()); int code; if( (code = sftpOpen(url, pflags, attr, handle)) != SSH2_FX_OK ) { error(ERR_CANNOT_OPEN_FOR_READING, url.prettyURL()); return; } TQ_UINT32 len = 1024; // Get first 1k for determining mimetype TQ_UINT64 offset = 0; code = SSH2_FX_OK; while( offset < len && code == SSH2_FX_OK ) { if( (code = sftpRead(handle, offset, len, mydata)) == SSH2_FX_OK ) { data(mydata); offset += mydata.size(); processedSize(offset); kdDebug(KIO_SFTP_DB) << "mimetype(): offset = " << offset << endl; } } data(TQByteArray()); 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; TQByteArray 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; TQCString path = remoteEncoding()->encode(url.path()); uint len = path.length(); sftpFileAttr attr(remoteEncoding()); if (permissions != -1) attr.setPermissions(permissions); TQ_UINT32 id, expectedId; id = expectedId = mMsgId++; TQByteArray p; TQDataStream s(p, IO_WriteOnly); s << TQ_UINT32(1 /*type*/ + 4 /*id*/ + 4 /*str length*/ + len + attr.size()); s << (TQ_UINT8)SSH2_FXP_MKDIR; s << id; s.writeBytes(path.data(), len); s << attr; kdDebug(KIO_SFTP_DB) << "mkdir(): packet size is " << p.size() << endl; putPacket(p); getPacket(p); TQ_UINT8 type; TQDataStream r(p, IO_ReadOnly); r >> type >> id; if( id != expectedId ) { kdError(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 TQString& 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){ TQString 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, TQString::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 : TQString::null), mConnected); } bool sftpProtocol::getPacket(TQByteArray& msg) { TQByteArray 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; TQDataStream s(buf, IO_ReadOnly); s >> msgLen; //kdDebug(KIO_SFTP_DB) << "getPacket(): Message size = " << msgLen << endl; msg.resize(0); TQBuffer b( msg ); b.open( IO_WriteOnly ); while( msgLen ) { len = atomicio(ssh.stdioFd(), buf.data(), kMin((uint)buf.size(), msgLen), true /*read*/); if( len == 0 || len == -1) { TQString 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(TQByteArray& 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; TQCString path = remoteEncoding()->encode(url.path()); uint len = path.length(); TQ_UINT32 id, expectedId; id = expectedId = mMsgId++; TQByteArray p; TQDataStream s(p, IO_WriteOnly); s << TQ_UINT32(1 /*type*/ + 4 /*id*/ + 4 /*str length*/ + len); s << (TQ_UINT8)SSH2_FXP_REALPATH; s << id; s.writeBytes(path.data(), len); putPacket(p); getPacket(p); TQ_UINT8 type; TQDataStream 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 ) { TQ_UINT32 code; r >> code; return code; } if( type != SSH2_FXP_NAME ) { kdError(KIO_SFTP_DB) << "sftpRealPath(): unexpected packet type of " << type << endl; return -1; } TQ_UINT32 count; r >> count; if( count != 1 ) { kdError(KIO_SFTP_DB) << "sftpRealPath(): Bad number of file attributes for realpath command" << endl; return -1; } TQCString newPath; r >> newPath; newPath.truncate(newPath.size()); if (newPath.isEmpty()) newPath = "/"; newUrl.setPath(newPath); return SSH2_FX_OK; } sftpProtocol::Status sftpProtocol::doProcessStatus(TQ_UINT8 code, const TQString& message) { Status res; res.code = 0; res.size = 0; res.text = message; switch(code) { case SSH2_FX_OK: case SSH2_FX_EOF: break; case SSH2_FX_NO_SUCH_FILE: res.code = ERR_DOES_NOT_EXIST; break; case SSH2_FX_PERMISSION_DENIED: res.code = ERR_ACCESS_DENIED; break; case SSH2_FX_FAILURE: res.text = i18n("SFTP command failed for an unknown reason."); res.code = ERR_UNKNOWN; break; case SSH2_FX_BAD_MESSAGE: res.text = i18n("The SFTP server received a bad message."); res.code = ERR_UNKNOWN; break; case SSH2_FX_OP_UNSUPPORTED: res.text = i18n("You attempted an operation unsupported by the SFTP server."); res.code = ERR_UNKNOWN; break; default: res.text = i18n("Error code: %1").arg(code); res.code = ERR_UNKNOWN; } return res; } /** Process SSH_FXP_STATUS packets. */ void sftpProtocol::processStatus(TQ_UINT8 code, const TQString& message){ Status st = doProcessStatus( code, message ); if( st.code != 0 ) error( st.code, st.text ); } /** Opens a directory handle for url.path. Returns true if succeeds. */ int sftpProtocol::sftpOpenDirectory(const KURL& url, TQByteArray& handle){ kdDebug(KIO_SFTP_DB) << "sftpOpenDirectory(" << url << ", handle)" << endl; TQCString path = remoteEncoding()->encode(url.path()); uint len = path.length(); TQ_UINT32 id, expectedId; id = expectedId = mMsgId++; TQByteArray p; TQDataStream s(p, IO_WriteOnly); s << (TQ_UINT32)(1 /*type*/ + 4 /*id*/ + 4 /*str length*/ + len); s << (TQ_UINT8)SSH2_FXP_OPENDIR; s << (TQ_UINT32)id; s.writeBytes(path.data(), len); putPacket(p); getPacket(p); TQDataStream r(p, IO_ReadOnly); TQ_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 ) { TQ_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 TQByteArray& handle){ kdDebug(KIO_SFTP_DB) << "sftpClose()" << endl; TQ_UINT32 id, expectedId; id = expectedId = mMsgId++; TQByteArray p; TQDataStream s(p, IO_WriteOnly); s << (TQ_UINT32)(1 /*type*/ + 4 /*id*/ + 4 /*str length*/ + handle.size()); s << (TQ_UINT8)SSH2_FXP_CLOSE; s << (TQ_UINT32)id; s << handle; putPacket(p); getPacket(p); TQDataStream r(p, IO_ReadOnly); TQ_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; } TQ_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; TQCString path = remoteEncoding()->encode(url.path()); uint len = path.length(); TQ_UINT32 id, expectedId; id = expectedId = mMsgId++; TQByteArray p; TQDataStream s(p, IO_WriteOnly); s << (TQ_UINT32)(1 /*type*/ + 4 /*id*/ + 4 /*str length*/ + len + attr.size()); s << (TQ_UINT8)SSH2_FXP_SETSTAT; s << (TQ_UINT32)id; s.writeBytes(path.data(), len); s << attr; putPacket(p); getPacket(p); TQDataStream r(p, IO_ReadOnly); TQ_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; } TQ_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; TQCString path = remoteEncoding()->encode(url.path()); uint len = path.length(); TQ_UINT32 id, expectedId; id = expectedId = mMsgId++; TQByteArray p; TQDataStream s(p, IO_WriteOnly); s << (TQ_UINT32)(1 /*type*/ + 4 /*id*/ + 4 /*str length*/ + len); s << (TQ_UINT8)(isfile ? SSH2_FXP_REMOVE : SSH2_FXP_RMDIR); s << (TQ_UINT32)id; s.writeBytes(path.data(), len); putPacket(p); getPacket(p); TQDataStream r(p, IO_ReadOnly); TQ_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; } TQ_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; TQCString srcPath = remoteEncoding()->encode(src.path()); TQCString destPath = remoteEncoding()->encode(dest.path()); uint slen = srcPath.length(); uint dlen = destPath.length(); TQ_UINT32 id, expectedId; id = expectedId = mMsgId++; TQByteArray p; TQDataStream s(p, IO_WriteOnly); s << (TQ_UINT32)(1 /*type*/ + 4 /*id*/ + 4 /*str length*/ + slen + 4 /*str length*/ + dlen); s << (TQ_UINT8)SSH2_FXP_RENAME; s << (TQ_UINT32)id; s.writeBytes(srcPath.data(), slen); s.writeBytes(destPath.data(), dlen); putPacket(p); getPacket(p); TQDataStream r(p, IO_ReadOnly); TQ_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 TQByteArray& handle, const KURL& url){ // url is needed so we can lookup the link destination kdDebug(KIO_SFTP_DB) << "sftpReadDir(): " << url << endl; TQ_UINT32 id, expectedId, count; TQ_UINT8 type; sftpFileAttr attr (remoteEncoding()); attr.setDirAttrsFlag(true); TQByteArray p; TQDataStream s(p, IO_WriteOnly); id = expectedId = mMsgId++; s << (TQ_UINT32)(1 /*type*/ + 4 /*id*/ + 4 /*str length*/ + handle.size()); s << (TQ_UINT8)SSH2_FXP_READDIR; s << (TQ_UINT32)id; s << handle; putPacket(p); getPacket(p); TQDataStream r(p, IO_ReadOnly); r >> type >> id; if( id != expectedId ) { kdError(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, TQString& target){ kdDebug(KIO_SFTP_DB) << "sftpReadLink(): " << url << endl; TQCString 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; TQ_UINT32 id, expectedId; id = expectedId = mMsgId++; TQByteArray p; TQDataStream s(p, IO_WriteOnly); s << (TQ_UINT32)(1 /*type*/ + 4 /*id*/ + 4 /*str length*/ + len); s << (TQ_UINT8)SSH2_FXP_READLINK; s << id; s.writeBytes(path.data(), len); putPacket(p); getPacket(p); TQ_UINT8 type; TQDataStream 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 ) { TQ_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; } TQ_UINT32 count; r >> count; if( count != 1 ) { kdError(KIO_SFTP_DB) << "sftpReadLink(): Bad number of file attributes for realpath command" << endl; return -1; } TQCString 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 TQString& _target, const KURL& dest){ TQCString destPath = remoteEncoding()->encode(dest.path()); TQCString target = remoteEncoding()->encode(_target); uint dlen = destPath.length(); uint tlen = target.length(); kdDebug(KIO_SFTP_DB) << "sftpSymLink(" << target << " -> " << destPath << ")" << endl; TQ_UINT32 id, expectedId; id = expectedId = mMsgId++; TQByteArray p; TQDataStream s(p, IO_WriteOnly); s << (TQ_UINT32)(1 /*type*/ + 4 /*id*/ + 4 /*str length*/ + tlen + 4 /*str length*/ + dlen); s << (TQ_UINT8)SSH2_FXP_SYMLINK; s << (TQ_UINT32)id; s.writeBytes(target.data(), tlen); s.writeBytes(destPath.data(), dlen); putPacket(p); getPacket(p); TQDataStream r(p, IO_ReadOnly); TQ_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; } TQ_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; TQCString path = remoteEncoding()->encode(url.path()); uint len = path.length(); TQ_UINT32 id, expectedId; id = expectedId = mMsgId++; TQByteArray p; TQDataStream s(p, IO_WriteOnly); s << (TQ_UINT32)(1 /*type*/ + 4 /*id*/ + 4 /*str length*/ + len); s << (TQ_UINT8)SSH2_FXP_LSTAT; s << (TQ_UINT32)id; s.writeBytes(path.data(), len); putPacket(p); getPacket(p); TQDataStream r(p, IO_ReadOnly); TQ_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 ) { TQ_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) ) { TQString 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 TQ_UINT32 pflags, const sftpFileAttr& attr, TQByteArray& handle) { kdDebug(KIO_SFTP_DB) << "sftpOpen(" << url << ", handle" << endl; TQCString path = remoteEncoding()->encode(url.path()); uint len = path.length(); TQ_UINT32 id, expectedId; id = expectedId = mMsgId++; TQByteArray p; TQDataStream s(p, IO_WriteOnly); s << (TQ_UINT32)(1 /*type*/ + 4 /*id*/ + 4 /*str length*/ + len + 4 /*pflags*/ + attr.size()); s << (TQ_UINT8)SSH2_FXP_OPEN; s << (TQ_UINT32)id; s.writeBytes(path.data(), len); s << pflags; s << attr; putPacket(p); getPacket(p); TQDataStream r(p, IO_ReadOnly); TQ_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 ) { TQ_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 TQByteArray& handle, KIO::filesize_t offset, TQ_UINT32 len, TQByteArray& data) { // kdDebug(KIO_SFTP_DB) << "sftpRead( offset = " << offset << ", len = " << len << ")" << endl; TQByteArray p; TQDataStream s(p, IO_WriteOnly); TQ_UINT32 id, expectedId; id = expectedId = mMsgId++; s << (TQ_UINT32)(1 /*type*/ + 4 /*id*/ + 4 /*str length*/ + handle.size() + 8 /*offset*/ + 4 /*length*/); s << (TQ_UINT8)SSH2_FXP_READ; s << (TQ_UINT32)id; s << handle; s << offset; // we don't have a convienient 64 bit int so set upper int to zero s << len; putPacket(p); getPacket(p); TQDataStream r(p, IO_ReadOnly); TQ_UINT8 type; r >> type >> id; if( id != expectedId ) { kdError(KIO_SFTP_DB) << "sftpRead: sftp packet id mismatch" << endl; return -1; } if( type == SSH2_FXP_STATUS ) { TQ_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 TQByteArray& handle, KIO::filesize_t offset, const TQByteArray& data){ // kdDebug(KIO_SFTP_DB) << "sftpWrite( offset = " << offset << // ", data sz = " << data.size() << ")" << endl; TQByteArray p; TQDataStream s(p, IO_WriteOnly); TQ_UINT32 id, expectedId; id = expectedId = mMsgId++; s << (TQ_UINT32)(1 /*type*/ + 4 /*id*/ + 4 /*str length*/ + handle.size() + 8 /*offset*/ + 4 /* data size */ + data.size()); s << (TQ_UINT8)SSH2_FXP_WRITE; s << (TQ_UINT32)id; s << handle; s << offset; // we don't have a convienient 64 bit int so set upper int to zero s << data; // 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; TQDataStream r(p, IO_ReadOnly); TQ_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; } TQ_UINT32 code; r >> code; return code; }