You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
kftpgrabber/kftpgrabber/src/engine/ftpsocket.cpp

2754 lines
90 KiB

/*
* This file is part of the KFTPGrabber project
*
* Copyright (C) 2003-2006 by the KFTPGrabber developers
* Copyright (C) 2003-2006 Jernej Kos <kostko@jweb-network.net>
*
* 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.
*
* This program is distributed in the hope that it will be useful, but
* is provided AS IS, WITHOUT ANY WARRANTY; without even the implied
* warranty of MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, and
* NON-INFRINGEMENT. See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Steet, Fifth Floor, Boston,
* MA 02110-1301, USA.
*
* In addition, as a special exception, the copyright holders give
* permission to link the code of portions of this program with the
* OpenSSL library under certain conditions as described in each
* individual source file, and distribute linked combinations
* including the two.
*
* You must obey the GNU General Public License in all respects
* for all of the code used other than OpenSSL. If you modify
* file(s) with this exception, you may extend this exception to your
* version of the file(s), but you are not obligated to do so. If you
* do not wish to do so, delete this exception statement from your
* version. If you delete this exception statement from all source
* files in the program, then also delete it here.
*/
#include "ftpsocket.h"
#include "thread.h"
#include "ftpdirectoryparser.h"
#include "cache.h"
#include "speedlimiter.h"
#include "ssl.h"
#include "misc/kftpotpgenerator.h"
#include "misc/kftpconfig.h"
#include <tqdir.h>
#include <tdelocale.h>
#include <kstandarddirs.h>
#include <tdesocketdevice.h>
#include <utime.h>
#include <stdlib.h>
#include <errno.h>
#include <sys/socket.h>
#include <netinet/in.h>
namespace KFTPEngine {
FtpSocket::FtpSocket(Thread *thread)
: KNetwork::KStreamSocket(),
Socket(thread, "ftp"),
SpeedLimiterItem(),
m_login(false),
m_transferSocket(0),
m_directoryParser(0),
m_controlConnecting(false),
m_controlSsl(0),
m_dataSsl(0),
m_clientCert(0)
{
enableRead(false);
setBlocking(false);
}
FtpSocket::~FtpSocket()
{
protoDisconnect();
}
void FtpSocket::poll()
{
if (m_controlConnecting) {
if (isFatalError(error())) {
slotError();
resetError();
m_controlConnecting = false;
return;
}
if (state() == Connected) {
m_controlConnecting = false;
slotConnected();
}
return;
}
slotControlTryRead();
if (!m_buffer.isEmpty())
processBuffer();
if (m_transferSocket) {
if (m_transferConnecting && m_transferSocket->state() == Connected) {
m_transferConnecting = false;
slotDataConnected();
} else if (!m_transferConnecting) {
if (getCurrentCommand() == Commands::CmdPut) {
if (m_transferStart >= 2)
slotDataTryWrite();
} else {
bool input;
m_transferSocket->socketDevice()->poll(&input, 0, 0, 0);
if (input)
slotDataTryRead();
}
}
} else if (m_serverSocket) {
bool input;
m_serverSocket->socketDevice()->poll(&input, 0, 0, 0);
if (input) {
KNetwork::KActiveSocketBase *socket = m_serverSocket->accept();
if (socket) {
slotDataAccept(static_cast<KNetwork::KStreamSocket*>(socket));
m_transferConnecting = false;
}
}
}
// Check for timeouts
// NOTE This should be moved to a TQTimer's slot when ported to TQt 4
timeoutCheck();
keepaliveCheck();
}
void FtpSocket::slotControlTryRead()
{
TQString tmpStr;
TQ_LONG size = 0;
// Read what we can
if (getConfigInt("ssl") && m_controlSsl) {
size = m_controlSsl->read(m_controlBuffer, sizeof(m_controlBuffer) - 1);
if (size == -1) {
protoDisconnect();
return;
}
} else
size = readBlock(m_controlBuffer, sizeof(m_controlBuffer) - 1);
if (error() != NoError) {
// Have we been disconnected ?
if (error() != WouldBlock)
protoDisconnect();
return;
}
if (size == 0)
return;
for (int i = 0; i < size; i++)
if (m_controlBuffer[i] == 0)
m_controlBuffer[i] = '!';
memset(m_controlBuffer + size, 0, sizeof(m_controlBuffer) - size);
m_buffer.append(m_controlBuffer);
}
void FtpSocket::processBuffer()
{
// Parse any lines we might have
int pos;
while ((pos = m_buffer.find('\n')) > -1) {
TQString line = m_buffer.mid(0, pos);
line = m_remoteEncoding->decode(TQCString(line.ascii()));
parseLine(line);
// Remove what we just parsed
m_buffer.remove(0, pos + 1);
}
}
void FtpSocket::parseLine(const TQString &line)
{
// Is this the end of multiline response ?
if (!m_multiLineCode.isEmpty() && line.left(4) == m_multiLineCode) {
m_multiLineCode = "";
emitEvent(Event::EventResponse, line);
} else if (line[3] == '-' && m_multiLineCode.isEmpty()) {
m_multiLineCode = line.left(3) + " ";
emitEvent(Event::EventMultiline, line);
} else if (!m_multiLineCode.isEmpty()) {
emitEvent(Event::EventMultiline, line);
} else {
// Normal response
emitEvent(Event::EventResponse, line);
}
timeoutWait(false);
// Parse our response
m_response = line;
nextCommand();
}
bool FtpSocket::isResponse(const TQString &code)
{
TQString ref;
if (isMultiline())
ref = m_multiLineCode;
else
ref = m_response;
return ref.left(code.length()) == code;
}
void FtpSocket::sendCommand(const TQString &command)
{
emitEvent(Event::EventCommand, command);
TQCString buffer(m_remoteEncoding->encode(command) + "\r\n");
if (getConfigInt("ssl") && m_controlSsl)
m_controlSsl->write(buffer.data(), buffer.length());
else
writeBlock(buffer.data(), buffer.length());
timeoutWait(true);
}
void FtpSocket::resetCommandClass(ResetCode code)
{
timeoutWait(false);
if (m_transferSocket && code != Ok) {
// Invalidate the socket
closeDataTransferSocket();
// Close the file that failed transfer
if (getTransferFile()->isOpen()) {
getTransferFile()->close();
if (getCurrentCommand() == Commands::CmdGet && getTransferFile()->size() == 0)
getTransferFile()->remove();
}
}
if (m_serverSocket && code != Ok)
delete m_serverSocket;
Socket::resetCommandClass(code);
}
// *******************************************************************************************
// ***************************************** CONNECT *****************************************
// *******************************************************************************************
class FtpCommandConnect : public Commands::Base {
public:
enum State {
None,
SentAuthTls,
SentUser,
SentPass,
SentPbsz,
SentProt,
DoingSyst,
DoingFeat,
SentPwd
};
ENGINE_STANDARD_COMMAND_CONSTRUCTOR(FtpCommandConnect, FtpSocket, CmdNone)
void process()
{
switch (currentState) {
case None: {
if (!socket()->isMultiline()) {
if (socket()->isResponse("2")) {
// Negotiate a SSL connection if configured
if (socket()->getConfigInt("ssl.use_tls")) {
currentState = SentAuthTls;
socket()->sendCommand("AUTH TLS");
} else {
// Send username
currentState = SentUser;
socket()->sendCommand("USER " + socket()->getCurrentUrl().user());
}
} else {
socket()->emitEvent(Event::EventMessage, i18n("Connection has failed."));
socket()->protoAbort();
socket()->emitError(ConnectFailed);
}
}
break;
}
case SentAuthTls: {
if (socket()->isResponse("2")) {
socket()->m_controlSsl = new Ssl(socket());
// Setup client certificate if one was provided
if (socket()->m_clientCert)
socket()->m_controlSsl->setClientCertificate(socket()->m_clientCert);
if (socket()->m_controlSsl->connect()) {
socket()->emitEvent(Event::EventMessage, i18n("SSL negotiation successful. Connection is secured with %1 bit cipher %2.").arg(socket()->m_controlSsl->connectionInfo().getCipherUsedBits()).arg(socket()->m_controlSsl->connectionInfo().getCipher()));
socket()->setConfig("ssl", 1);
// Now send the username
currentState = SentUser;
socket()->sendCommand("USER " + socket()->getCurrentUrl().user());
} else {
delete socket()->m_controlSsl;
socket()->m_controlSsl = 0;
socket()->emitEvent(Event::EventMessage, i18n("SSL negotiation failed. Login aborted."));
socket()->resetCommandClass(Failed);
socket()->protoAbort();
}
} else {
socket()->emitEvent(Event::EventMessage, i18n("SSL negotiation request failed. Login aborted."));
socket()->resetCommandClass(Failed);
socket()->protoAbort();
}
break;
}
case SentUser: {
if (socket()->isResponse("331")) {
// Send password
if (socket()->isResponse("331 Response to otp-") ||
socket()->isResponse("331 Response to s/key")) {
// OTP: 331 Response to otp-md5 41 or4828 ext required for foo.
TQString tmp = socket()->getResponse();
tmp = tmp.section(' ', 3, 5);
KFTPOTPGenerator otp(tmp, socket()->getCurrentUrl().pass());
currentState = SentPass;
socket()->sendCommand("PASS " + otp.generateOTP());
} else {
socket()->sendCommand("PASS " + socket()->getCurrentUrl().pass());
currentState = SentPass;
}
} else if (socket()->isResponse("230")) {
// Some servers imediately send the 230 response for anonymous accounts
if (!socket()->isMultiline()) {
if (socket()->getConfigInt("ssl")) {
currentState = SentPbsz;
socket()->sendCommand("PBSZ 0");
} else {
// Do SYST
socket()->sendCommand("SYST");
currentState = DoingSyst;
}
}
} else {
socket()->emitEvent(Event::EventMessage, i18n("Login has failed."));
socket()->protoAbort();
socket()->emitError(LoginFailed);
}
break;
}
case SentPass: {
if (socket()->isResponse("230")) {
if (!socket()->isMultiline()) {
if (socket()->getConfigInt("ssl")) {
currentState = SentPbsz;
socket()->sendCommand("PBSZ 0");
} else {
// Do SYST
socket()->sendCommand("SYST");
currentState = DoingSyst;
}
}
} else {
socket()->emitEvent(Event::EventMessage, i18n("Login has failed."));
socket()->protoAbort();
socket()->emitError(LoginFailed);
}
break;
}
case SentPbsz: {
currentState = SentProt;
TQString prot = "PROT ";
if (socket()->getConfigInt("ssl.prot_mode") == 0)
prot.append('P');
else
prot.append('C');
socket()->sendCommand(prot);
break;
}
case SentProt: {
if (socket()->isResponse("5")) {
// Fallback to unencrypted data channel
socket()->setConfig("ssl.prot_mode", 2);
}
currentState = DoingSyst;
socket()->sendCommand("SYST");
break;
}
case DoingSyst: {
socket()->sendCommand("FEAT");
currentState = DoingFeat;
break;
}
case DoingFeat: {
if (socket()->isMultiline()) {
parseFeat();
} else {
socket()->sendCommand("PWD");
currentState = SentPwd;
}
break;
}
case SentPwd: {
// Parse the current working directory
if (socket()->isResponse("2")) {
// 257 "/home/default/path"
TQString tmp = socket()->getResponse();
int first = tmp.find('"') + 1;
tmp = tmp.mid(first, tmp.findRev('"') - first);
socket()->setDefaultDirectory(tmp);
socket()->setCurrentDirectory(tmp);
}
// Enable transmission of keepalive events
socket()->keepaliveStart();
currentState = None;
socket()->emitEvent(Event::EventMessage, i18n("Connected."));
socket()->emitEvent(Event::EventConnect);
socket()->m_login = true;
socket()->resetCommandClass();
break;
}
}
}
void parseFeat()
{
TQString feat = socket()->getResponse().stripWhiteSpace().upper();
if (feat.left(3).toInt() > 0 && feat[3] == '-')
feat.remove(0, 4);
if (feat.left(4) == "MDTM") {
// Server has MDTM (MoDification TiMe) support
socket()->setConfig("feat.mdtm", 1);
} else if (feat.left(4) == "PRET") {
// Server is a distributed ftp server and requires PRET for transfers
socket()->setConfig("feat.pret", 1);
} else if (feat.left(4) == "MLSD") {
// Server supports machine-friendly directory listings
socket()->setConfig("feat.mlsd", 1);
} else if (feat.left(4) == "REST") {
// Server supports resume operations
socket()->setConfig("feat.rest", 1);
} else if (feat.left(4) == "SSCN") {
// Server supports SSCN for secure site-to-site transfers
socket()->setConfig("feat.sscn", 1);
socket()->setConfig("feat.cpsv", 0);
} else if (feat.left(4) == "CPSV" && !socket()->getConfigInt("feat.sscn")) {
// Server supports CPSV for secure site-to-site transfers
socket()->setConfig("feat.cpsv", 1);
}
}
};
void FtpSocket::protoConnect(const KURL &url)
{
emitEvent(Event::EventState, i18n("Connecting..."));
emitEvent(Event::EventMessage, i18n("Connecting to %1:%2...").arg(url.host()).arg(url.port()));
if (!getConfig("encoding").isEmpty())
changeEncoding(getConfig("encoding"));
// Start the connect procedure
m_controlConnecting = true;
setCurrentUrl(url);
KNetwork::KStreamSocket::connect(url.host(), TQString::number(url.port()));
}
void FtpSocket::slotConnected()
{
if (getConfigInt("ssl.use_implicit")) {
m_controlSsl = new Ssl(this);
// Setup client certificate if one was provided
if (m_clientCert)
m_controlSsl->setClientCertificate(m_clientCert);
if (m_controlSsl->connect()) {
emitEvent(Event::EventMessage, i18n("SSL negotiation successful. Connection is secured with %1 bit cipher %2.").arg(m_controlSsl->connectionInfo().getCipherUsedBits()).arg(m_controlSsl->connectionInfo().getCipher()));
setConfig("ssl", 1);
} else {
delete m_controlSsl;
m_controlSsl = 0;
emitEvent(Event::EventMessage, i18n("SSL negotiation failed. Connect aborted."));
resetCommandClass(Failed);
protoAbort();
}
}
timeoutWait(true);
emitEvent(Event::EventState, i18n("Logging in..."));
emitEvent(Event::EventMessage, i18n("Connected with server, waiting for welcome message..."));
setupCommandClass(FtpCommandConnect);
}
void FtpSocket::slotError()
{
if (isFatalError(error())) {
emitEvent(Event::EventMessage, i18n("Failed to connect (%1)").arg(errorString(error())));
emitError(ConnectFailed);
resetCommandClass(FailedSilently);
}
}
// *******************************************************************************************
// **************************************** DISCONNECT ***************************************
// *******************************************************************************************
void FtpSocket::protoDisconnect()
{
Socket::protoDisconnect();
// Close SSL
if (getConfigInt("ssl") && m_controlSsl) {
m_controlSsl->close();
delete m_controlSsl;
m_controlSsl = 0;
if (m_clientCert) {
delete m_clientCert;
m_clientCert = 0;
}
}
// Terminate the connection
m_login = false;
KNetwork::KStreamSocket::close();
}
void FtpSocket::protoAbort()
{
Socket::protoAbort();
if (getCurrentCommand() != Commands::CmdNone) {
// Abort current command
if (getCurrentCommand() == Commands::CmdConnect)
protoDisconnect();
if (m_cmdData)
resetCommandClass(UserAbort);
emitEvent(Event::EventMessage, i18n("Aborted."));
}
}
// *******************************************************************************************
// ********************************* NEGOTIATE DATA CONNECTION *******************************
// *******************************************************************************************
class FtpCommandNegotiateData : public Commands::Base {
public:
enum State {
None,
SentSscnOff,
SentType,
SentProt,
SentPret,
NegotiateActive,
NegotiatePasv,
NegotiateEpsv,
HaveConnection,
SentRest,
SentDataCmd,
WaitTransfer
};
ENGINE_STANDARD_COMMAND_CONSTRUCTOR(FtpCommandNegotiateData, FtpSocket, CmdNone)
void process()
{
switch (currentState) {
case None: {
if (socket()->getConfigInt("sscn.activated")) {
// First disable SSCN
currentState = SentSscnOff;
socket()->sendCommand("SSCN OFF");
return;
}
}
case SentSscnOff: {
if (currentState == SentSscnOff)
socket()->setConfig("sscn.activated", 0);
// Change type
currentState = SentType;
socket()->resetTransferStart();
TQString type = "TYPE ";
type.append(socket()->getConfigInt("params.data_type"));
socket()->sendCommand(type);
break;
}
case SentType: {
if (socket()->getConfigInt("ssl") && socket()->getConfigInt("ssl.prot_mode") == 1) {
currentState = SentProt;
if (socket()->getPreviousCommand() == Commands::CmdList)
socket()->sendCommand("PROT P");
else
socket()->sendCommand("PROT C");
} else if (socket()->getConfigInt("feat.pret")) {
currentState = SentPret;
socket()->sendCommand("PRET " + socket()->getConfig("params.data_command"));
} else {
negotiateDataConnection();
}
break;
}
case SentProt: {
if (socket()->getConfigInt("feat.pret")) {
currentState = SentPret;
socket()->sendCommand("PRET " + socket()->getConfig("params.data_command"));
} else {
negotiateDataConnection();
}
break;
}
case SentPret: {
// PRET failed because of filesystem problems, abort right away!
if (socket()->isResponse("530")) {
socket()->emitError(PermissionDenied);
socket()->resetCommandClass(Failed);
return;
} else if (socket()->isResponse("550")) {
socket()->emitError(FileNotFound);
socket()->resetCommandClass(Failed);
return;
} else if (socket()->isResponse("5")) {
// PRET is not supported, disable for future use
socket()->setConfig("feat.pret", 0);
}
negotiateDataConnection();
break;
}
case NegotiateActive: negotiateActive(); break;
case NegotiateEpsv: negotiateEpsv(); break;
case NegotiatePasv: negotiatePasv(); break;
case HaveConnection: {
// We have the connection
if (socket()->getConfigInt("params.data_rest_do")) {
currentState = SentRest;
socket()->sendCommand("REST " + TQString::number(socket()->getConfigFs("params.data_rest")));
} else {
currentState = SentDataCmd;
socket()->sendCommand(socket()->getConfig("params.data_command"));
}
break;
}
case SentRest: {
if (!socket()->isResponse("2") && !socket()->isResponse("3")) {
socket()->setConfig("feat.rest", 0);
socket()->getTransferFile()->close();
bool ok;
if (socket()->getPreviousCommand() == Commands::CmdGet)
ok = socket()->getTransferFile()->open(IO_WriteOnly | IO_Truncate);
else
ok = socket()->getTransferFile()->open(IO_ReadOnly);
// Check if there was a problem opening the file
if (!ok) {
socket()->emitError(FileOpenFailed);
socket()->resetCommandClass(Failed);
return;
}
}
// We have sent REST, now send the data command
currentState = SentDataCmd;
socket()->sendCommand(socket()->getConfig("params.data_command"));
break;
}
case SentDataCmd: {
if (!socket()->isResponse("1")) {
// Some problems while executing the data command
socket()->resetCommandClass(Failed);
return;
}
if (!socket()->isMultiline()) {
socket()->checkTransferStart();
currentState = WaitTransfer;
}
break;
}
case WaitTransfer: {
if (!socket()->isResponse("2")) {
// Transfer has failed
socket()->resetCommandClass(Failed);
return;
}
if (!socket()->isMultiline()) {
// Transfer has been completed
socket()->checkTransferEnd();
}
break;
}
}
}
void negotiateDataConnection()
{
if (socket()->getConfigInt("feat.epsv")) {
negotiateEpsv();
} else if (socket()->getConfigInt("feat.pasv")) {
negotiatePasv();
} else {
negotiateActive();
}
}
void negotiateEpsv()
{
if (currentState == NegotiateEpsv) {
if (!socket()->isResponse("2")) {
// Negotiation failed
socket()->setConfig("feat.epsv", "0");
// Try the next thing
negotiateDataConnection();
return;
}
// 229 Entering Extended Passive Mode (|||55016|)
const char *begin = strchr(socket()->getResponse().ascii(), '(');
int port;
if (!begin || sscanf(begin, "(|||%d|)", &port) != 1) {
// Unable to parse, try the next thing
socket()->setConfig("feat.epsv", "0");
negotiateDataConnection();
return;
}
// We have the address, let's setup the transfer socket and then
// we are done.
currentState = HaveConnection;
socket()->setupPassiveTransferSocket(TQString::null, port);
} else {
// Just send the EPSV command
currentState = NegotiateEpsv;
socket()->sendCommand("EPSV");
}
}
void negotiatePasv()
{
if (currentState == NegotiatePasv) {
if (!socket()->isResponse("2")) {
// Negotiation failed
socket()->setConfig("feat.pasv", "0");
// Try the next thing
negotiateDataConnection();
return;
}
// Ok PASV command successfull - let's parse the result
int ip[6];
const char *begin = strchr(socket()->getResponse().ascii(), '(');
// Some stinky servers don't respect RFC and do it on their own
if (!begin) {
delete &begin;
const char *begin = strchr(socket()->getResponse().ascii(), '=');
}
if (!begin || (sscanf(begin, "(%d,%d,%d,%d,%d,%d)",&ip[0], &ip[1], &ip[2], &ip[3], &ip[4], &ip[5]) != 6 &&
sscanf(begin, "=%d,%d,%d,%d,%d,%d",&ip[0], &ip[1], &ip[2], &ip[3], &ip[4], &ip[5]) != 6)) {
// Unable to parse, try the next thing
socket()->setConfig("feat.pasv", "0");
negotiateDataConnection();
return;
}
// Convert to string
TQString host;
int port;
host.sprintf("%d.%d.%d.%d", ip[0], ip[1], ip[2], ip[3]);
port = ip[4] << 8 | ip[5];
// If the reported IP address is from a private IP range, this might be because the
// remote server is not properly configured. So we just use the server's real IP instead
// of the one we got (if the host is really local, then this should work as well).
if (!socket()->getConfigInt("feat.pret")) {
if (host.startsWith("192.168.") || host.startsWith("10.") || host.startsWith("172.16."))
host = socket()->peerAddress().nodeName();
}
// We have the address, let's setup the transfer socket and then
// we are done.
currentState = HaveConnection;
socket()->setupPassiveTransferSocket(host, port);
} else {
// Just send the PASV command
currentState = NegotiatePasv;
socket()->sendCommand("PASV");
}
}
void negotiateActive()
{
if (currentState == NegotiateActive) {
if (!socket()->isResponse("2")) {
if (socket()->getConfigInt("feat.eprt")) {
socket()->setConfig("feat.eprt", 0);
} else {
// Negotiation failed, reset since active is the last fallback
socket()->resetCommandClass(Failed);
return;
}
} else {
currentState = HaveConnection;
socket()->nextCommandAsync();
return;
}
}
// Setup the socket and set the apropriate port command
currentState = NegotiateActive;
KNetwork::TDESocketAddress address = socket()->setupActiveTransferSocket();
if (address.address()) {
if (socket()->getConfigInt("feat.eprt")) {
TQString ianaFamily = TQString::number(address.ianaFamily());
socket()->sendCommand("EPRT |" + ianaFamily + "|" + address.nodeName() + "|" + address.serviceName() + "|");
} else if (address.ianaFamily() == 1) {
TQString format = address.nodeName().replace(".", ",");
format.append(",");
format.append(TQString::number((unsigned char) address.address()->sa_data[0]));
format.append(",");
format.append(TQString::number((unsigned char) address.address()->sa_data[1]));
socket()->sendCommand("PORT " + format);
} else {
socket()->emitEvent(Event::EventMessage, i18n("Incompatible address family for PORT, but EPRT not supported, aborting!"));
socket()->resetCommandClass(Failed);
}
}
}
};
void FtpSocket::initializeTransferSocket()
{
m_transferConnecting = true;
m_transferEnd = 0;
m_transferBytes = 0;
m_transferBufferSize = 4096;
m_transferBuffer = (char*) malloc(m_transferBufferSize);
m_speedLastTime = time(0);
m_speedLastBytes = 0;
// Setup the speed limiter
switch (getPreviousCommand()) {
case Commands::CmdGet: SpeedLimiter::self()->append(this, SpeedLimiter::Download); break;
case Commands::CmdPut: SpeedLimiter::self()->append(this, SpeedLimiter::Upload); break;
default: break;
}
m_transferSocket->enableRead(false);
m_transferSocket->setBlocking(false);
m_transferSocket->setAddressReuseable(true);
}
void FtpSocket::setupPassiveTransferSocket(const TQString &host, int port)
{
// Use the host from control connection if empty
TQString realHost = host;
if (host.isEmpty() || getConfigInt("pasv.use_site_ip"))
realHost = peerAddress().nodeName();
// Let's connect
emitEvent(Event::EventMessage, i18n("Establishing data connection with %1:%2...").arg(realHost).arg(port));
if (!m_transferSocket)
m_transferSocket = new KNetwork::KStreamSocket();
initializeTransferSocket();
m_transferSocket->connect(realHost, TQString::number(port));
}
KNetwork::TDESocketAddress FtpSocket::setupActiveTransferSocket()
{
if (!m_serverSocket)
m_serverSocket = new KNetwork::TDEServerSocket();
m_serverSocket->setAcceptBuffered(false);
m_serverSocket->setFamily(KNetwork::KResolver::InetFamily);
if (KFTPCore::Config::activeForcePort()) {
// Bind only to ports in a specified portrange
bool found = false;
unsigned int max = KFTPCore::Config::activeMaxPort();
unsigned int min = KFTPCore::Config::activeMinPort();
for (unsigned int port = min + rand() % (max - min + 1); port <= max; port++) {
m_serverSocket->setAddress(TQString::number(port));
bool success = m_serverSocket->listen();
if (found = (success && m_serverSocket->error() == TDESocketBase::NoError))
break;
m_serverSocket->close();
}
if (!found) {
emitEvent(Event::EventMessage, i18n("Unable to establish a listening socket."));
resetCommandClass(Failed);
return KNetwork::TDESocketAddress();
}
} else {
m_serverSocket->setAddress("0");
if (!m_serverSocket->listen()) {
emitEvent(Event::EventMessage, i18n("Unable to establish a listening socket."));
resetCommandClass(Failed);
return KNetwork::TDESocketAddress();
}
}
KNetwork::TDESocketAddress serverAddr = m_serverSocket->localAddress();
KNetwork::TDESocketAddress controlAddr = localAddress();
KNetwork::TDESocketAddress request;
if (KFTPCore::Config::portForceIp() && !getConfigInt("active.no_force_ip")) {
TQString remoteIp = peerAddress().nodeName();
if (KFTPCore::Config::ignoreExternalIpForLan() &&
(remoteIp.startsWith("192.168.") || remoteIp.startsWith("10.") || remoteIp.startsWith("172.16."))) {
request = controlAddr;
} else {
// Force a specified IP/hostname to be used in PORT
KNetwork::KResolverResults resolverResults;
resolverResults = KNetwork::KResolver::resolve(KFTPCore::Config::portIp(), "21");
if (resolverResults.error() < 0) {
// Well, we are unable to resolve the name, so we should use what we got
// from control socket
request = controlAddr;
} else {
// The name has been resolved and we have the address, so we should
// use it
request = resolverResults[0].address();
}
}
} else {
// Just use our IP we bound to when connecting to the remote server
request = controlAddr;
}
// Set the proper port
request.address()->sa_data[0] = serverAddr.address()->sa_data[0];
request.address()->sa_data[1] = serverAddr.address()->sa_data[1];
emitEvent(Event::EventMessage, i18n("Waiting for data connection on port %1...").arg(serverAddr.serviceName()));
return request;
}
void FtpSocket::slotDataAccept(KNetwork::KStreamSocket *socket)
{
m_transferSocket = socket;
initializeTransferSocket();
// Socket has been accepted so the server is not needed anymore
delete m_serverSocket;
emitEvent(Event::EventMessage, i18n("Data connection established."));
checkTransferStart();
}
void FtpSocket::closeDataTransferSocket()
{
if (m_dataSsl) {
m_dataSsl->close();
delete m_dataSsl;
m_dataSsl = 0;
}
// Free the buffer and invalidate the socket
free(m_transferBuffer);
m_transferSocket->close();
delete m_transferSocket;
m_transferBytes = 0;
SpeedLimiter::self()->remove(this);
}
void FtpSocket::transferCompleted()
{
// Transfer has been completed, cleanup
closeDataTransferSocket();
checkTransferEnd();
}
void FtpSocket::checkTransferStart()
{
if (++m_transferStart >= 2) {
// Setup SSL data connection
if (getConfigInt("ssl") && (getConfigInt("ssl.prot_mode") == 0 ||
(getConfigInt("ssl.prot_mode") == 1 && getToplevelCommand() == Commands::CmdList)) && !m_dataSsl) {
m_dataSsl = new Ssl(m_transferSocket);
if (m_dataSsl->connect()) {
emitEvent(Event::EventMessage, i18n("Data channel secured with %1 bit SSL.").arg(m_dataSsl->connectionInfo().getCipherUsedBits()));
} else {
emitEvent(Event::EventMessage, i18n("SSL negotiation for the data channel has failed. Aborting transfer."));
resetCommandClass(Failed);
return;
}
}
}
}
void FtpSocket::checkTransferEnd()
{
if (++m_transferEnd >= 2) {
emitEvent(Event::EventMessage, i18n("Transfer completed."));
resetCommandClass();
}
}
void FtpSocket::slotDataConnected()
{
emitEvent(Event::EventMessage, i18n("Data connection established."));
checkTransferStart();
nextCommand();
}
void FtpSocket::variableBufferUpdate(TQ_LONG size)
{
if (size > m_transferBufferSize - 64) {
if (m_transferBufferSize + 512 <= 32768) {
m_transferBufferSize += 512;
m_transferBuffer = (char*) realloc(m_transferBuffer, m_transferBufferSize);
}
} else if (size < m_transferBufferSize - 65) {
if (m_transferBufferSize - 512 >= 4096) {
m_transferBufferSize -= 512;
m_transferBuffer = (char*) realloc(m_transferBuffer, m_transferBufferSize);
}
}
}
void FtpSocket::slotDataTryWrite()
{
bool updateVariableBuffer = true;
// Enforce speed limits
if (allowedBytes() > -1) {
m_transferBufferSize = allowedBytes();
if (m_transferBufferSize > 32768)
m_transferBufferSize = 32768;
else if (m_transferBufferSize == 0)
return;
m_transferBuffer = (char*) realloc(m_transferBuffer, m_transferBufferSize);
updateVariableBuffer = false;
} else if (m_transferBufferSize == 0) {
m_transferBufferSize = 4096;
m_transferBuffer = (char*) realloc(m_transferBuffer, m_transferBufferSize);
}
if (!getTransferFile()->isOpen())
return;
// If there is nothing to upload, just close the connection right away
if (getTransferFile()->size() == 0) {
transferCompleted();
return;
}
TQFile::Offset tmpOffset = getTransferFile()->at();
TQ_LONG readSize = getTransferFile()->readBlock(m_transferBuffer, m_transferBufferSize);
TQ_LONG size = 0;
if (m_dataSsl)
size = m_dataSsl->write(m_transferBuffer, readSize);
else
size = m_transferSocket->writeBlock(m_transferBuffer, readSize);
if (size < 0) {
getTransferFile()->at(tmpOffset);
return;
} else if (size < readSize)
getTransferFile()->at(tmpOffset + size);
m_transferBytes += size;
updateUsage(size);
timeoutPing();
if (getTransferFile()->atEnd()) {
// We have reached the end of file, so we should terminate the connection
transferCompleted();
return;
}
if (updateVariableBuffer)
variableBufferUpdate(size);
}
void FtpSocket::slotDataTryRead()
{
bool updateVariableBuffer = true;
// Enforce speed limits
if (allowedBytes() > -1) {
m_transferBufferSize = allowedBytes();
if (m_transferBufferSize > 32768)
m_transferBufferSize = 32768;
else if (m_transferBufferSize == 0)
return;
m_transferBuffer = (char*) realloc(m_transferBuffer, m_transferBufferSize);
updateVariableBuffer = false;
} else if (m_transferBufferSize == 0) {
m_transferBufferSize = 4096;
m_transferBuffer = (char*) realloc(m_transferBuffer, m_transferBufferSize);
}
TQ_LONG size = 0;
if (m_dataSsl) {
size = m_dataSsl->read(m_transferBuffer, m_transferBufferSize);
if (size == -1) {
transferCompleted();
return;
}
} else {
size = m_transferSocket->readBlock(m_transferBuffer, m_transferBufferSize);
// Check if the connection has been closed
if (m_transferSocket->error() != NoError) {
if (m_transferSocket->error() != WouldBlock) {
transferCompleted();
return;
}
}
}
if (size <= 0) {
if (!m_dataSsl)
transferCompleted();
return;
}
updateUsage(size);
timeoutPing();
switch (getPreviousCommand()) {
case Commands::CmdList: {
// Feed the data to the directory listing parser
if (m_directoryParser)
m_directoryParser->addData(m_transferBuffer, size);
break;
}
case Commands::CmdGet: {
// Write to file
getTransferFile()->writeBlock(m_transferBuffer, size);
m_transferBytes += size;
break;
}
default: {
tqDebug("WARNING: slotDataReadActivity called for an invalid command!");
return;
}
}
if (updateVariableBuffer)
variableBufferUpdate(size);
}
// *******************************************************************************************
// ******************************************* LIST ******************************************
// *******************************************************************************************
class FtpCommandList : public Commands::Base {
public:
enum State {
None,
SentCwd,
SentStat,
WaitList
};
ENGINE_STANDARD_COMMAND_CONSTRUCTOR(FtpCommandList, FtpSocket, CmdList)
TQString path;
void process()
{
switch (currentState) {
case None: {
path = socket()->getConfig("params.list.path");
if (socket()->isChained())
socket()->m_lastDirectoryListing = DirectoryListing();
// Change working directory
currentState = SentCwd;
socket()->changeWorkingDirectory(path);
break;
}
case SentCwd: {
if (!socket()->getConfigInt("status.cwd")) {
// Change directory has failed and we should be silent (=error reporting is off)
socket()->resetCommandClass();
return;
}
// Check the directory listing cache
DirectoryListing cached = Cache::self()->findCached(socket(), socket()->getCurrentDirectory());
if (cached.isValid()) {
socket()->emitEvent(Event::EventMessage, i18n("Using cached directory listing."));
if (socket()->isChained()) {
// We don't emit an event, because this list has been called from another
// command. Just save the listing.
socket()->m_lastDirectoryListing = cached;
} else
socket()->emitEvent(Event::EventDirectoryListing, cached);
socket()->resetCommandClass();
return;
}
socket()->m_directoryParser = new FtpDirectoryParser(socket());
// Support for faster stat directory listings over the control connection
if (socket()->getConfigInt("stat_listings")) {
currentState = SentStat;
socket()->sendCommand("STAT .");
return;
}
// First we have to initialize the data connection, another class will
// do this for us, so we just add it to the command chain
socket()->setConfig("params.data_rest_do", 0);
socket()->setConfig("params.data_type", 'A');
if (socket()->getConfigInt("feat.mlsd"))
socket()->setConfig("params.data_command", "MLSD");
else
socket()->setConfig("params.data_command", "LIST -a");
currentState = WaitList;
chainCommandClass(FtpCommandNegotiateData);
break;
}
case SentStat: {
if (!socket()->isResponse("2")) {
// The server doesn't support STAT, disable it and fallback
socket()->setConfig("stat_listings", 0);
socket()->setConfig("params.data_rest_do", 0);
socket()->setConfig("params.data_type", 'A');
if (socket()->getConfigInt("feat.mlsd"))
socket()->setConfig("params.data_command", "MLSD");
else
socket()->setConfig("params.data_command", "LIST -a");
currentState = WaitList;
chainCommandClass(FtpCommandNegotiateData);
return;
} else if (socket()->isMultiline()) {
// Some servers put the response code into the multiline reply
TQString response = socket()->getResponse();
if (response.left(3) == "211")
response = response.mid(4);
socket()->m_directoryParser->addDataLine(response);
return;
}
// If we are done, just go on and emit the listing
}
case WaitList: {
// List has been received
if (socket()->isChained()) {
// We don't emit an event, because this list has been called from another
// command. Just save the listing.
socket()->m_lastDirectoryListing = socket()->m_directoryParser->getListing();
} else
socket()->emitEvent(Event::EventDirectoryListing, socket()->m_directoryParser->getListing());
// Cache the directory listing
Cache::self()->addDirectory(socket(), socket()->m_directoryParser->getListing());
delete socket()->m_directoryParser;
socket()->m_directoryParser = 0;
socket()->resetCommandClass();
break;
}
}
}
};
void FtpSocket::protoList(const KURL &path)
{
emitEvent(Event::EventState, i18n("Fetching directory listing..."));
emitEvent(Event::EventMessage, i18n("Fetching directory listing..."));
// Set the directory that should be listed
setConfig("params.list.path", path.path());
activateCommandClass(FtpCommandList);
}
// *******************************************************************************************
// ******************************************* GET *******************************************
// *******************************************************************************************
class FtpCommandGet : public Commands::Base {
public:
enum State {
None,
SentCwd,
SentMdtm,
StatDone,
DestChecked,
WaitTransfer
};
ENGINE_STANDARD_COMMAND_CONSTRUCTOR(FtpCommandGet, FtpSocket, CmdGet)
KURL sourceFile;
KURL destinationFile;
time_t modificationTime;
void process()
{
switch (currentState) {
case None: {
modificationTime = 0;
sourceFile.setPath(socket()->getConfig("params.get.source"));
destinationFile.setPath(socket()->getConfig("params.get.destination"));
// Attempt to CWD to the parent directory
currentState = SentCwd;
socket()->changeWorkingDirectory(sourceFile.directory());
break;
}
case SentCwd: {
// Send MDTM
if (socket()->getConfigInt("feat.mdtm")) {
currentState = SentMdtm;
socket()->sendCommand("MDTM " + sourceFile.path());
break;
} else {
// Don't break so we will get on to checking for file existance
}
}
case SentMdtm: {
if (currentState == SentMdtm) {
if (socket()->isResponse("550")) {
// The file probably doesn't exist, just ignore it
} else if (!socket()->isResponse("213")) {
socket()->setConfig("feat.mdtm", 0);
} else {
// Parse MDTM response
struct tm dt = {0,0,0,0,0,0,0,0,0,0,0};
TQString tmp(socket()->getResponse());
tmp.remove(0, 4);
dt.tm_year = tmp.left(4).toInt() - 1900;
dt.tm_mon = tmp.mid(4, 2).toInt() - 1;
dt.tm_mday = tmp.mid(6, 2).toInt();
dt.tm_hour = tmp.mid(8, 2).toInt();
dt.tm_min = tmp.mid(10, 2).toInt();
dt.tm_sec = tmp.mid(12, 2).toInt();
modificationTime = mktime(&dt);
}
}
// Check if the local file exists and stat the remote file if so
if (TQDir::root().exists(destinationFile.path())) {
socket()->protoStat(sourceFile);
currentState = StatDone;
return;
} else {
TDEStandardDirs::makeDir(destinationFile.directory());
// Don't break so we will get on to initiating the data connection
}
}
case StatDone: {
if (currentState == StatDone) {
DirectoryListing list;
list.addEntry(socket()->getStatResponse());
currentState = DestChecked;
socket()->emitEvent(Event::EventFileExists, list);
return;
}
}
case DestChecked: {
socket()->setConfig("params.data_rest_do", 0);
if (isWakeup()) {
// We have been waken up because a decision has been made
FileExistsWakeupEvent *event = static_cast<FileExistsWakeupEvent*>(m_wakeupEvent);
if (!socket()->getConfigInt("feat.rest") && event->action == FileExistsWakeupEvent::Resume)
event->action = FileExistsWakeupEvent::Overwrite;
switch (event->action) {
case FileExistsWakeupEvent::Rename: {
// Change the destination filename, otherwise it is the same as overwrite
destinationFile.setPath(event->newFileName);
}
case FileExistsWakeupEvent::Overwrite: {
socket()->getTransferFile()->setName(destinationFile.path());
socket()->getTransferFile()->open(IO_WriteOnly | IO_Truncate);
if (socket()->getConfigInt("feat.rest")) {
socket()->setConfig("params.data_rest_do", 1);
socket()->setConfig("params.data_rest", 0);
}
break;
}
case FileExistsWakeupEvent::Resume: {
socket()->getTransferFile()->setName(destinationFile.path());
socket()->getTransferFile()->open(IO_WriteOnly | IO_Append);
// Signal resume
socket()->emitEvent(Event::EventResumeOffset, socket()->getTransferFile()->size());
socket()->setConfig("params.data_rest_do", 1);
socket()->setConfig("params.data_rest", (filesize_t) socket()->getTransferFile()->size());
break;
}
case FileExistsWakeupEvent::Skip: {
// Transfer should be aborted
socket()->emitEvent(Event::EventTransferComplete);
socket()->resetCommandClass();
return;
}
}
} else {
// The file doesn't exist so we are free to overwrite
socket()->getTransferFile()->setName(destinationFile.path());
socket()->getTransferFile()->open(IO_WriteOnly | IO_Truncate);
}
// Check if there was a problem opening the file
if (!socket()->getTransferFile()->isOpen()) {
socket()->emitError(FileOpenFailed);
socket()->resetCommandClass(Failed);
return;
}
// First we have to initialize the data connection, another class will
// do this for us, so we just add it to the command chain
socket()->setConfig("params.data_type", KFTPCore::Config::self()->ftpMode(sourceFile.path()));
socket()->setConfig("params.data_command", "RETR " + sourceFile.filename());
currentState = WaitTransfer;
chainCommandClass(FtpCommandNegotiateData);
break;
}
case WaitTransfer: {
// Transfer has been completed
socket()->getTransferFile()->close();
if (modificationTime != 0) {
// Use the modification time we got from MDTM
utimbuf tmp;
tmp.actime = time(0);
tmp.modtime = modificationTime;
utime(destinationFile.path().latin1(), &tmp);
}
socket()->emitEvent(Event::EventTransferComplete);
socket()->emitEvent(Event::EventReloadNeeded);
socket()->resetCommandClass();
break;
}
}
}
};
void FtpSocket::protoGet(const KURL &source, const KURL &destination)
{
emitEvent(Event::EventState, i18n("Transfering..."));
emitEvent(Event::EventMessage, i18n("Downloading file '%1'...").arg(source.fileName()));
// Set the source and destination
setConfig("params.get.source", source.path());
setConfig("params.get.destination", destination.path());
activateCommandClass(FtpCommandGet);
}
// *******************************************************************************************
// ******************************************* CWD *******************************************
// *******************************************************************************************
class FtpCommandCwd : public Commands::Base {
public:
enum State {
None,
SentCwd,
SentPwd,
SentMkd,
SentCwdEnd
};
ENGINE_STANDARD_COMMAND_CONSTRUCTOR(FtpCommandCwd, FtpSocket, CmdNone)
TQString targetDirectory;
TQString currentPathPart;
TQString cached;
int currentPart;
int numParts;
bool shouldCreate;
void process()
{
switch (currentState) {
case None: {
targetDirectory = socket()->getConfig("params.cwd.path");
socket()->setConfig("status.cwd", 1);
// If we are already there, no need to CWD
if (socket()->getCurrentDirectory() == targetDirectory) {
socket()->resetCommandClass();
return;
}
cached = Cache::self()->findCachedPath(socket(), targetDirectory);
if (!cached.isEmpty()) {
if (socket()->getCurrentDirectory() == cached) {
// We are already there
socket()->resetCommandClass();
return;
}
}
// First check the toplevel directory and if it exists we are done
currentState = SentCwd;
currentPart = 0;
numParts = targetDirectory.contains('/');
shouldCreate = socket()->getConfigInt("params.cwd.create");
socket()->sendCommand("CWD " + targetDirectory);
break;
}
case SentCwd: {
if (socket()->isMultiline())
return;
if (socket()->isResponse("250") && currentPart == 0) {
if (!cached.isEmpty()) {
socket()->setCurrentDirectory(cached);
socket()->resetCommandClass();
} else {
// Directory exists, check where we are
currentState = SentPwd;
socket()->sendCommand("PWD");
}
} else {
// Changing the working directory has failed
if (shouldCreate) {
currentPathPart = targetDirectory.section('/', 0, ++currentPart);
currentState = SentMkd;
socket()->sendCommand("MKD " + currentPathPart);
} else if (socket()->errorReporting()) {
socket()->emitError(socket()->getPreviousCommand() == Commands::CmdList ? ListFailed : FileNotFound);
socket()->resetCommandClass(Failed);
} else {
socket()->setConfig("status.cwd", 0);
socket()->resetCommandClass();
}
}
break;
}
case SentPwd: {
// Parse the current working directory
if (socket()->isResponse("2")) {
TQString tmp = socket()->getResponse();
int first = tmp.find('"') + 1;
tmp = tmp.mid(first, tmp.findRev('"') - first);
// Set the current directory and cache it
socket()->setCurrentDirectory(tmp);
Cache::self()->addPath(socket(), tmp);
socket()->resetCommandClass();
} else if (socket()->errorReporting()) {
socket()->emitError(socket()->getPreviousCommand() == Commands::CmdList ? ListFailed : FileNotFound);
socket()->resetCommandClass(Failed);
} else {
socket()->setConfig("status.cwd", 0);
socket()->resetCommandClass();
}
break;
}
case SentMkd: {
// Invalidate parent cache
if (socket()->isResponse("2")) {
Cache::self()->invalidateEntry(socket(), KURL(currentPathPart).directory());
}
if (currentPart == numParts) {
// We are done, since all directories have been created
currentState = SentCwdEnd;
socket()->sendCommand("CWD " + targetDirectory);
} else {
currentPathPart = targetDirectory.section('/', 0, ++currentPart);
currentState = SentMkd;
socket()->sendCommand("MKD " + currentPathPart);
}
break;
}
case SentCwdEnd: {
if (socket()->isMultiline())
return;
// See where we are and set current working directory
currentState = SentPwd;
socket()->sendCommand("PWD");
break;
}
}
}
};
void FtpSocket::changeWorkingDirectory(const TQString &path, bool shouldCreate)
{
// Set the path to cwd to
setConfig("params.cwd.path", path);
setConfig("params.cwd.create", shouldCreate);
activateCommandClass(FtpCommandCwd);
}
// *******************************************************************************************
// ******************************************* PUT *******************************************
// *******************************************************************************************
class FtpCommandPut : public Commands::Base {
public:
enum State {
None,
WaitCwd,
SentSize,
StatDone,
DestChecked,
WaitTransfer
};
ENGINE_STANDARD_COMMAND_CONSTRUCTOR(FtpCommandPut, FtpSocket, CmdPut)
KURL sourceFile;
KURL destinationFile;
bool fetchedSize;
filesize_t destinationSize;
void cleanup()
{
// Unclean upload termination, be sure to erase the cached stat infos
Cache::self()->invalidateEntry(socket(), destinationFile.directory());
}
void process()
{
switch (currentState) {
case None: {
sourceFile.setPath(socket()->getConfig("params.get.source"));
destinationFile.setPath(socket()->getConfig("params.get.destination"));
fetchedSize = false;
// Check if the local file exists
if (!TQDir::root().exists(sourceFile.path())) {
socket()->emitError(FileNotFound);
socket()->resetCommandClass(Failed);
return;
}
// Change to the current working directory, creating any directories that are
// still missing
currentState = WaitCwd;
socket()->changeWorkingDirectory(destinationFile.directory(), true);
break;
}
case WaitCwd: {
// Check if the remote file exists
if (socket()->getConfigInt("feat.size")) {
currentState = SentSize;
socket()->sendCommand("SIZE " + destinationFile.path());
} else {
// SIZE is not available, try stat directly
currentState = StatDone;
socket()->protoStat(destinationFile);
}
break;
}
case SentSize: {
if (socket()->isResponse("213")) {
destinationSize = socket()->getResponse().mid(4).toULongLong();
fetchedSize = true;
// File exists, we have to stat to get more data
currentState = StatDone;
socket()->protoStat(destinationFile);
} else if (socket()->isResponse("500") || socket()->getResponse().contains("Operation not permitted", false)) {
// Yes, some servers don't support the SIZE command :/
socket()->setConfig("feat.size", 0);
currentState = StatDone;
socket()->protoStat(destinationFile);
} else {
currentState = DestChecked;
process();
}
break;
}
case StatDone: {
if (!socket()->getStatResponse().filename().isEmpty()) {
if (fetchedSize) {
if (socket()->getStatResponse().size() != destinationSize) {
// It would seem that the size has changed, cached data is invalid
Cache::self()->invalidateEntry(socket(), destinationFile.directory());
currentState = StatDone;
socket()->protoStat(destinationFile);
return;
}
}
// Remote file exists, emit a request for action
DirectoryListing list;
list.addEntry(socket()->getStatResponse());
currentState = DestChecked;
socket()->emitEvent(Event::EventFileExists, list);
return;
}
// Don't break here
}
case DestChecked: {
socket()->setConfig("params.data_rest_do", 0);
if (isWakeup()) {
// We have been waken up because a decision has been made
FileExistsWakeupEvent *event = static_cast<FileExistsWakeupEvent*>(m_wakeupEvent);
if (!socket()->getConfigInt("feat.rest") && event->action == FileExistsWakeupEvent::Resume)
event->action = FileExistsWakeupEvent::Overwrite;
switch (event->action) {
case FileExistsWakeupEvent::Rename: {
// Change the destination filename, otherwise it is the same as overwrite
destinationFile.setPath(event->newFileName);
}
case FileExistsWakeupEvent::Overwrite: {
socket()->getTransferFile()->setName(sourceFile.path());
socket()->getTransferFile()->open(IO_ReadOnly);
if (socket()->getConfigInt("feat.rest")) {
socket()->setConfig("params.data_rest_do", 1);
socket()->setConfig("params.data_rest", 0);
}
break;
}
case FileExistsWakeupEvent::Resume: {
socket()->getTransferFile()->setName(sourceFile.path());
socket()->getTransferFile()->open(IO_ReadOnly);
socket()->getTransferFile()->at(socket()->getStatResponse().size());
// Signal resume
socket()->emitEvent(Event::EventResumeOffset, socket()->getStatResponse().size());
socket()->setConfig("params.data_rest_do", 1);
socket()->setConfig("params.data_rest", (filesize_t) socket()->getStatResponse().size());
break;
}
case FileExistsWakeupEvent::Skip: {
// Transfer should be aborted
markClean();
socket()->resetCommandClass(UserAbort);
socket()->emitEvent(Event::EventTransferComplete);
return;
}
}
} else {
// The file doesn't exist so we are free to overwrite
socket()->getTransferFile()->setName(sourceFile.path());
socket()->getTransferFile()->open(IO_ReadOnly);
}
// Check if there was a problem opening the file
if (!socket()->getTransferFile()->isOpen()) {
socket()->emitError(FileOpenFailed);
socket()->resetCommandClass(Failed);
return;
}
// First we have to initialize the data connection, another class will
// do this for us, so we just add it to the command chain
socket()->setConfig("params.data_type", KFTPCore::Config::self()->ftpMode(destinationFile.path()));
socket()->setConfig("params.data_command", "STOR " + destinationFile.filename());
currentState = WaitTransfer;
chainCommandClass(FtpCommandNegotiateData);
break;
}
case WaitTransfer: {
// Transfer has been completed
Cache::self()->updateDirectoryEntry(socket(), destinationFile, socket()->getTransferFile()->size());
socket()->getTransferFile()->close();
markClean();
socket()->emitEvent(Event::EventTransferComplete);
socket()->emitEvent(Event::EventReloadNeeded);
socket()->resetCommandClass();
break;
}
}
}
};
void FtpSocket::protoPut(const KURL &source, const KURL &destination)
{
emitEvent(Event::EventState, i18n("Transfering..."));
emitEvent(Event::EventMessage, i18n("Uploading file '%1'...").arg(source.fileName()));
// Set the source and destination
setConfig("params.get.source", source.path());
setConfig("params.get.destination", destination.path());
activateCommandClass(FtpCommandPut);
}
// *******************************************************************************************
// **************************************** REMOVE *******************************************
// *******************************************************************************************
class FtpCommandRemove : public Commands::Base {
public:
enum State {
None,
SentCwd,
SentRemove
};
ENGINE_STANDARD_COMMAND_CONSTRUCTOR(FtpCommandRemove, FtpSocket, CmdNone)
TQString destinationPath;
TQString parentDirectory;
void process()
{
switch (currentState) {
case None: {
destinationPath = socket()->getConfig("params.remove.path");
parentDirectory = socket()->getConfig("params.remove.parent");
currentState = SentRemove;
if (socket()->getConfigInt("params.remove.directory")) {
if (socket()->getCurrentDirectory() != parentDirectory) {
// We should change working directory to parent directory before removing
currentState = SentCwd;
socket()->sendCommand("CWD " + parentDirectory);
} else {
socket()->sendCommand("RMD " + destinationPath);
}
} else {
socket()->sendCommand("DELE " + destinationPath);
}
break;
}
case SentCwd: {
if (socket()->isMultiline())
return;
if (socket()->isResponse("2")) {
// CWD was successful
socket()->setCurrentDirectory(parentDirectory);
}
currentState = SentRemove;
socket()->sendCommand("RMD " + destinationPath);
break;
}
case SentRemove: {
if (socket()->isMultiline())
return;
if (!socket()->isResponse("2")) {
socket()->resetCommandClass(Failed);
} else {
// Invalidate cached parent entry (if any)
Cache::self()->invalidateEntry(socket(), parentDirectory);
Cache::self()->invalidatePath(socket(), destinationPath);
if (!socket()->isChained())
socket()->emitEvent(Event::EventReloadNeeded);
socket()->resetCommandClass();
}
break;
}
}
}
};
void FtpSocket::protoRemove(const KURL &path)
{
emitEvent(Event::EventState, i18n("Removing..."));
// Set the file to remove
setConfig("params.remove.parent", path.directory());
setConfig("params.remove.path", path.path());
activateCommandClass(FtpCommandRemove);
}
// *******************************************************************************************
// **************************************** RENAME *******************************************
// *******************************************************************************************
class FtpCommandRename : public Commands::Base {
public:
enum State {
None,
SentRnfr,
SentRnto
};
ENGINE_STANDARD_COMMAND_CONSTRUCTOR(FtpCommandRename, FtpSocket, CmdRename)
TQString sourcePath;
TQString destinationPath;
void process()
{
switch (currentState) {
case None: {
sourcePath = socket()->getConfig("params.rename.source");
destinationPath = socket()->getConfig("params.rename.destination");
currentState = SentRnfr;
socket()->sendCommand("RNFR " + sourcePath);
break;
}
case SentRnfr: {
if (socket()->isResponse("3")) {
currentState = SentRnto;
socket()->sendCommand("RNTO " + destinationPath);
} else
socket()->resetCommandClass(Failed);
break;
}
case SentRnto: {
if (socket()->isResponse("2")) {
// Invalidate cached parent entry (if any)
Cache::self()->invalidateEntry(socket(), KURL(sourcePath).directory());
Cache::self()->invalidateEntry(socket(), KURL(destinationPath).directory());
Cache::self()->invalidatePath(socket(), sourcePath);
Cache::self()->invalidatePath(socket(), destinationPath);
socket()->emitEvent(Event::EventReloadNeeded);
socket()->resetCommandClass();
} else
socket()->resetCommandClass(Failed);
break;
}
}
}
};
void FtpSocket::protoRename(const KURL &source, const KURL &destination)
{
emitEvent(Event::EventState, i18n("Renaming..."));
// Set rename options
setConfig("params.rename.source", source.path());
setConfig("params.rename.destination", destination.path());
activateCommandClass(FtpCommandRename);
}
// *******************************************************************************************
// **************************************** CHMOD ********************************************
// *******************************************************************************************
class FtpCommandChmod : public Commands::Base {
public:
enum State {
None,
SentChmod
};
ENGINE_STANDARD_COMMAND_CONSTRUCTOR(FtpCommandChmod, FtpSocket, CmdChmod)
void process()
{
switch (currentState) {
case None: {
currentState = SentChmod;
TQString chmod;
chmod.sprintf("SITE CHMOD %.3d %s", socket()->getConfigInt("params.chmod.mode"),
socket()->getConfig("params.chmod.path").ascii());
socket()->sendCommand(chmod);
break;
}
case SentChmod: {
if (!socket()->isResponse("2"))
socket()->resetCommandClass(Failed);
else {
// Invalidate cached parent entry (if any)
Cache::self()->invalidateEntry(socket(), KURL(socket()->getConfig("params.chmod.path")).directory());
socket()->emitEvent(Event::EventReloadNeeded);
socket()->resetCommandClass();
}
break;
}
}
}
};
void FtpSocket::protoChmodSingle(const KURL &path, int mode)
{
emitEvent(Event::EventState, i18n("Changing mode..."));
// Set chmod options
setConfig("params.chmod.path", path.path());
setConfig("params.chmod.mode", mode);
activateCommandClass(FtpCommandChmod);
}
// *******************************************************************************************
// **************************************** MKDIR ********************************************
// *******************************************************************************************
class FtpCommandMkdir : public Commands::Base {
public:
enum State {
None,
SentMkdir
};
ENGINE_STANDARD_COMMAND_CONSTRUCTOR(FtpCommandMkdir, FtpSocket, CmdMkdir)
void process()
{
switch (currentState) {
case None: {
currentState = SentMkdir;
socket()->changeWorkingDirectory(socket()->getConfig("params.mkdir.path"), true);
break;
}
case SentMkdir: {
// Invalidate cached parent entry (if any)
Cache::self()->invalidateEntry(socket(), KURL(socket()->getCurrentDirectory()).directory());
socket()->emitEvent(Event::EventReloadNeeded);
socket()->resetCommandClass();
break;
}
}
}
};
void FtpSocket::protoMkdir(const KURL &path)
{
emitEvent(Event::EventState, i18n("Making directory..."));
setConfig("params.mkdir.path", path.path());
activateCommandClass(FtpCommandMkdir);
}
// *******************************************************************************************
// ******************************************* RAW *******************************************
// *******************************************************************************************
class FtpCommandRaw : public Commands::Base {
public:
enum State {
None,
SentRaw
};
ENGINE_STANDARD_COMMAND_CONSTRUCTOR(FtpCommandRaw, FtpSocket, CmdRaw)
TQString response;
void process()
{
switch (currentState) {
case None: {
currentState = SentRaw;
socket()->sendCommand(socket()->getConfig("params.raw.command"));
break;
}
case SentRaw: {
response.append(socket()->getResponse());
if (!socket()->isMultiline()) {
socket()->emitEvent(Event::EventRaw, response);
socket()->resetCommandClass();
}
break;
}
}
}
};
void FtpSocket::protoRaw(const TQString &raw)
{
setConfig("params.raw.command", raw);
activateCommandClass(FtpCommandRaw);
}
// *******************************************************************************************
// ******************************************* FXP *******************************************
// *******************************************************************************************
class FtpCommandFxp : public Commands::Base {
public:
enum State {
None,
// Source socket
SourceSentCwd,
SourceSentStat,
SourceDestVerified,
SourceSentType,
SourceSentSscn,
SourceSentProt,
SourceWaitType,
SourceSentPret,
SourceSentPasv,
SourceDoRest,
SourceSentRest,
SourceDoRetr,
SourceSentRetr,
SourceWaitTransfer,
SourceResetProt,
// Destination socket
DestSentStat,
DestWaitCwd,
DestDoType,
DestSentType,
DestSentSscn,
DestSentProt,
DestDoPort,
DestSentPort,
DestSentRest,
DestDoStor,
DestSentStor,
DestWaitTransfer,
DestResetProt
};
enum ProtectionMode {
ProtClear = 0,
ProtPrivate = 1,
ProtSSCN = 2
};
enum TransferMode {
TransferPASV = 0,
TransferCPSV = 1
};
ENGINE_STANDARD_COMMAND_CONSTRUCTOR(FtpCommandFxp, FtpSocket, CmdFxp)
FtpSocket *companion;
KURL sourceFile;
KURL destinationFile;
filesize_t resumeOffset;
void cleanup()
{
// We have been interrupted, so we have to abort the companion as well
if (!socket()->getConfigInt("params.fxp.abort")) {
companion->setConfig("params.fxp.abort", 1);
companion->protoAbort();
}
// Unclean upload termination, be sure to erase the cached stat infos
if (!socket()->getConfigInt("params.fxp.keep_cache"))
Cache::self()->invalidateEntry(socket(), destinationFile.directory());
}
void process()
{
switch (currentState) {
case None: {
sourceFile.setPath(socket()->getConfig("params.fxp.source"));
destinationFile.setPath(socket()->getConfig("params.fxp.destination"));
socket()->setConfig("params.fxp.keep_cache", 0);
// Who are we ? Where shall we begin ?
if (socket()->getConfigInt("params.fxp.companion")) {
// We are the companion, so we should check the destination
socket()->setConfig("params.fxp.companion", 0);
currentState = DestSentStat;
socket()->protoStat(destinationFile);
return;
} else {
socket()->setConfig("params.transfer.mode", TransferPASV);
if (socket()->getCurrentDirectory() != sourceFile.directory()) {
// Attempt to CWD to the parent directory
currentState = SourceSentCwd;
socket()->sendCommand("CWD " + sourceFile.directory());
return;
}
}
}
// ***************************************************************************
// ***************************** Source socket *******************************
// ***************************************************************************
case SourceSentCwd: {
if (currentState == SourceSentCwd) {
if (!socket()->isResponse("250")) {
socket()->emitError(FileNotFound);
socket()->resetCommandClass(Failed);
return;
}
if (socket()->isMultiline())
return;
else
socket()->setCurrentDirectory(sourceFile.directory());
}
// We are the source socket, let's stat
currentState = SourceSentStat;
socket()->protoStat(sourceFile);
break;
}
case SourceSentStat: {
if (socket()->getStatResponse().filename().isEmpty()) {
socket()->emitError(FileNotFound);
socket()->resetCommandClass(Failed);
} else {
// File exists, invoke the companion
companion->setConfig("params.fxp.companion", 1);
companion->thread()->siteToSite(socket()->thread(), sourceFile, destinationFile);
currentState = SourceDestVerified;
}
break;
}
case SourceDestVerified: {
if (isWakeup()) {
// We have been waken up because a decision has been made
FileExistsWakeupEvent *event = static_cast<FileExistsWakeupEvent*>(m_wakeupEvent);
if (!socket()->getConfigInt("feat.rest") && event->action == FileExistsWakeupEvent::Resume)
event->action = FileExistsWakeupEvent::Overwrite;
switch (event->action) {
case FileExistsWakeupEvent::Rename: {
// Change the destination filename, otherwise it is the same as overwrite
destinationFile.setPath(event->newFileName);
}
case FileExistsWakeupEvent::Overwrite: {
companion->setConfig("params.fxp.rest", 0);
resumeOffset = 0;
break;
}
case FileExistsWakeupEvent::Resume: {
companion->setConfig("params.fxp.rest", companion->getStatResponse().size());
resumeOffset = companion->getStatResponse().size();
break;
}
case FileExistsWakeupEvent::Skip: {
// Transfer should be aborted
companion->setConfig("params.fxp.keep_cache", 1);
socket()->setConfig("params.fxp.keep_cache", 1);
socket()->resetCommandClass(UserAbort);
socket()->emitEvent(Event::EventTransferComplete);
return;
}
}
} else {
companion->setConfig("params.fxp.rest", 0);
resumeOffset = 0;
}
// Change type
currentState = SourceSentType;
TQString type = "TYPE ";
type.append(KFTPCore::Config::self()->ftpMode(sourceFile.path()));
socket()->sendCommand(type);
break;
}
case SourceSentType: {
if (socket()->getConfigInt("ssl") && socket()->getConfigInt("ssl.prot_mode") != 2 && !socket()->getConfigInt("sscn.activated")) {
if (socket()->getConfigInt("ssl.prot_mode") == 0) {
if (socket()->getConfigInt("feat.sscn")) {
// We support SSCN
currentState = SourceSentSscn;
socket()->sendCommand("SSCN ON");
companion->setConfig("params.ssl.mode", ProtPrivate);
} else if (companion->getConfigInt("feat.sscn")) {
// Companion supports SSCN
currentState = SourceWaitType;
companion->setConfig("params.ssl.mode", ProtSSCN);
companion->nextCommandAsync();
} else if (socket()->getConfigInt("feat.cpsv")) {
// We support CPSV
currentState = SourceWaitType;
socket()->setConfig("params.transfer.mode", TransferCPSV);
companion->setConfig("params.ssl.mode", ProtPrivate);
companion->nextCommandAsync();
} else {
// Neither support SSCN, can't do SSL transfer
socket()->emitEvent(Event::EventMessage, i18n("Neither server supports SSCN/CPSV but SSL data connection requested, aborting transfer!"));
socket()->resetCommandClass(Failed);
return;
}
} else {
currentState = SourceSentProt;
socket()->sendCommand("PROT C");
companion->setConfig("params.ssl.mode", ProtClear);
}
} else {
currentState = SourceWaitType;
companion->nextCommandAsync();
}
break;
}
case SourceSentSscn: {
if (!socket()->isResponse("2")) {
socket()->resetCommandClass(Failed);
} else {
socket()->setConfig("sscn.activated", 1);
socket()->setConfig("params.fxp.changed_prot", 0);
currentState = SourceWaitType;
companion->nextCommandAsync();
}
break;
}
case SourceSentProt: {
if (!socket()->isResponse("2")) {
socket()->resetCommandClass(Failed);
} else {
socket()->setConfig("params.fxp.changed_prot", 1);
currentState = SourceWaitType;
companion->nextCommandAsync();
}
break;
}
case SourceWaitType: {
// We are ready to invoke file transfer, do PASV
if (socket()->getConfigInt("feat.pret")) {
currentState = SourceSentPret;
socket()->sendCommand("PRET RETR " + sourceFile.filename());
} else {
currentState = SourceSentPasv;
switch (socket()->getConfigInt("params.transfer.mode")) {
case TransferPASV: socket()->sendCommand("PASV"); break;
case TransferCPSV: socket()->sendCommand("CPSV"); break;
}
}
break;
}
case SourceSentPret: {
if (!socket()->isResponse("2")) {
if (socket()->isResponse("550")) {
socket()->emitError(PermissionDenied);
socket()->resetCommandClass(Failed);
return;
} else if (socket()->isResponse("530")) {
socket()->emitError(FileNotFound);
socket()->resetCommandClass(Failed);
}
socket()->setConfig("feat.pret", 0);
}
currentState = SourceSentPasv;
switch (socket()->getConfigInt("params.transfer.mode")) {
case TransferPASV: socket()->sendCommand("PASV"); break;
case TransferCPSV: socket()->sendCommand("CPSV"); break;
}
break;
}
case SourceSentPasv: {
// Parse the PASV response and get it to the companion to issue PORT
if (!socket()->isResponse("2")) {
socket()->resetCommandClass(Failed);
} else {
TQString tmp = socket()->getResponse();
int pos = tmp.find('(') + 1;
tmp = tmp.mid(pos, tmp.find(')') - pos);
currentState = SourceDoRest;
companion->setConfig("params.fxp.ip", tmp);
companion->nextCommandAsync();
}
break;
}
case SourceDoRest: {
currentState = SourceSentRest;
socket()->sendCommand("REST " + TQString::number(resumeOffset));
break;
}
case SourceSentRest: {
if (!socket()->isResponse("2") && !socket()->isResponse("3")) {
socket()->setConfig("feat.rest", 0);
companion->setConfig("params.fxp.rest", 0);
} else {
// Signal resume
socket()->emitEvent(Event::EventResumeOffset, resumeOffset);
}
currentState = SourceDoRetr;
companion->nextCommandAsync();
break;
}
case SourceDoRetr: {
currentState = SourceSentRetr;
socket()->sendCommand("RETR " + sourceFile.filename());
break;
}
case SourceSentRetr: {
if (!socket()->isResponse("1")) {
socket()->resetCommandClass(Failed);
} else {
currentState = SourceWaitTransfer;
}
break;
}
case SourceWaitTransfer: {
if (!socket()->isMultiline()) {
// Transfer has been completed
if (socket()->getConfigInt("params.fxp.changed_prot")) {
currentState = SourceResetProt;
TQString prot = "PROT ";
if (socket()->getConfigInt("ssl.prot_mode") == 0)
prot.append('P');
else
prot.append('C');
socket()->sendCommand(prot);
} else {
markClean();
socket()->emitEvent(Event::EventMessage, i18n("Transfer completed."));
socket()->emitEvent(Event::EventTransferComplete);
socket()->resetCommandClass();
}
}
break;
}
case SourceResetProt: {
markClean();
socket()->emitEvent(Event::EventMessage, i18n("Transfer completed."));
socket()->emitEvent(Event::EventTransferComplete);
socket()->resetCommandClass();
break;
}
// ***************************************************************************
// *************************** Destination socket ****************************
// ***************************************************************************
case DestSentStat: {
if (socket()->getStatResponse().filename().isEmpty()) {
// Change the working directory
currentState = DestWaitCwd;
socket()->changeWorkingDirectory(destinationFile.directory(), true);
} else {
// The file already exists, request action
DirectoryListing list;
list.addEntry(companion->getStatResponse());
list.addEntry(socket()->getStatResponse());
currentState = DestDoType;
socket()->emitEvent(Event::EventFileExists, list);
}
break;
}
case DestWaitCwd: {
// Directory has been changed/created, call back the companion
currentState = DestDoType;
companion->nextCommandAsync();
break;
}
case DestDoType: {
currentState = DestSentType;
TQString type = "TYPE ";
type.append(KFTPCore::Config::self()->ftpMode(sourceFile.path()));
socket()->sendCommand(type);
break;
}
case DestSentType: {
if (socket()->getConfigInt("ssl")) {
// Check what the source socket has instructed us to do
switch (socket()->getConfigInt("params.ssl.mode")) {
case ProtClear: {
// We should use cleartext data channel
if (socket()->getConfigInt("ssl.prot_mode") != 2) {
currentState = DestSentProt;
socket()->sendCommand("PROT C");
} else {
currentState = DestDoPort;
companion->nextCommandAsync();
}
break;
}
case ProtPrivate: {
// We should use private data channel
if (socket()->getConfigInt("ssl.prot_mode") != 0) {
currentState = DestSentProt;
socket()->sendCommand("PROT P");
} else {
currentState = DestDoPort;
companion->nextCommandAsync();
}
break;
}
case ProtSSCN: {
// We should initialize SSCN mode
if (!socket()->getConfigInt("sscn.activated")) {
currentState = DestSentSscn;
socket()->sendCommand("SSCN ON");
} else {
currentState = DestDoPort;
companion->nextCommandAsync();
}
break;
}
}
} else {
currentState = DestDoPort;
companion->nextCommandAsync();
}
break;
}
case DestSentSscn: {
if (!socket()->isResponse("2")) {
socket()->resetCommandClass(Failed);
} else {
socket()->setConfig("sscn.activated", 1);
socket()->setConfig("params.fxp.changed_prot", 0);
currentState = DestDoPort;
companion->nextCommandAsync();
}
break;
}
case DestSentProt: {
if (!socket()->isResponse("2")) {
socket()->resetCommandClass(Failed);
} else {
socket()->setConfig("params.fxp.changed_prot", 1);
currentState = DestDoPort;
companion->nextCommandAsync();
}
break;
}
case DestDoPort: {
currentState = DestSentPort;
socket()->sendCommand("PORT " + socket()->getConfig("params.fxp.ip"));
break;
}
case DestSentPort: {
if (!socket()->isResponse("2")) {
socket()->resetCommandClass(Failed);
} else {
currentState = DestSentRest;
socket()->sendCommand("REST " + socket()->getConfig("params.fxp.rest"));
}
break;
}
case DestSentRest: {
// We are ready for file transfer
currentState = DestDoStor;
companion->nextCommandAsync();
break;
}
case DestDoStor: {
currentState = DestSentStor;
socket()->sendCommand("STOR " + destinationFile.filename());
break;
}
case DestSentStor: {
if (!socket()->isResponse("1")) {
socket()->resetCommandClass(Failed);
} else {
currentState = DestWaitTransfer;
companion->nextCommandAsync();
}
break;
}
case DestWaitTransfer: {
if (!socket()->isMultiline()) {
// Transfer has been completed
if (socket()->getConfigInt("params.fxp.changed_prot")) {
currentState = DestResetProt;
TQString prot = "PROT ";
if (socket()->getConfigInt("ssl.prot_mode") == 0)
prot.append('P');
else
prot.append('C');
socket()->sendCommand(prot);
} else {
markClean();
socket()->emitEvent(Event::EventMessage, i18n("Transfer completed."));
socket()->emitEvent(Event::EventReloadNeeded);
socket()->resetCommandClass();
}
}
break;
}
case DestResetProt: {
markClean();
socket()->emitEvent(Event::EventMessage, i18n("Transfer completed."));
socket()->emitEvent(Event::EventReloadNeeded);
socket()->resetCommandClass();
break;
}
}
}
};
void FtpSocket::protoSiteToSite(Socket *socket, const KURL &source, const KURL &destination)
{
emitEvent(Event::EventState, i18n("Transfering..."));
emitEvent(Event::EventMessage, i18n("Transfering file '%1'...").arg(source.fileName()));
// Set the source and destination
setConfig("params.fxp.abort", 0);
setConfig("params.fxp.source", source.path());
setConfig("params.fxp.destination", destination.path());
FtpCommandFxp *fxp = new FtpCommandFxp(this);
fxp->companion = static_cast<FtpSocket*>(socket);
m_cmdData = fxp;
m_cmdData->process();
}
// *******************************************************************************************
// ******************************************* NOOP ******************************************
// *******************************************************************************************
class FtpCommandKeepAlive : public Commands::Base {
public:
enum State {
None,
SentNoop
};
ENGINE_STANDARD_COMMAND_CONSTRUCTOR(FtpCommandKeepAlive, FtpSocket, CmdKeepAlive)
void process()
{
switch (currentState) {
case None: {
currentState = SentNoop;
socket()->sendCommand("NOOP");
break;
}
case SentNoop: {
socket()->resetCommandClass();
break;
}
}
}
};
void FtpSocket::protoKeepAlive()
{
emitEvent(Event::EventState, i18n("Transmitting keep-alive..."));
setCurrentCommand(Commands::CmdKeepAlive);
activateCommandClass(FtpCommandKeepAlive);
}
}
#include "ftpsocket.moc"