#include <CoreServices/../Frameworks/CarbonCore.framework/Headers/MacErrors.h>
#include "ftp-protocol.h"
#include "netparameters.h"
namespace Security {
namespace Network {
FTPProtocol::FTPProtocol(Manager &mgr) : Protocol(mgr, "ftp")
{
}
FTPProtocol::FTPTransfer *FTPProtocol::makeTransfer(const Target &target, Operation operation)
{
return new FTPTransfer(*this, target, operation);
}
FTPProtocol::FTPConnection::FTPConnection(Protocol &proto, const HostTarget &hostTarget)
: TCPConnection(proto, hostTarget), state(errorState), mImageMode(false),
mDataPath(*this)
{
const HostTarget &host = proxyHostTarget();
connect(host.host(), host.port());
state = loginInitial;
}
void FTPProtocol::FTPConnection::request(const char *path)
{
assert(isDocked());
mOperationPath = path;
if (state == idle) startCommand(); }
void FTPProtocol::FTPConnection::startCommand()
{
observe(Observer::resourceFound);
switch (operation()) {
case makeDirectory:
printfe("MKD %s", mOperationPath.c_str());
state = directCommandSent;
return;
case removeDirectory:
printfe("RMD %s", mOperationPath.c_str());
state = directCommandSent;
return;
case removeFile:
printfe("DELE %s", mOperationPath.c_str());
state = directCommandSent;
return;
case genericCommand:
printfe("%s", mOperationPath.c_str());
state = directCommandSent;
return;
}
bool wantImageMode;
switch (operation()) {
case downloadDirectory:
case downloadListing:
wantImageMode = false;
break;
case download:
case upload:
wantImageMode = getv<string>(kNetworkFtpTransferMode, "I") == "I";
break;
default:
assert(false);
}
if (mImageMode != wantImageMode) {
printfe("TYPE %s", wantImageMode ? "I" : "A");
mImageMode = wantImageMode; state = typeCommandSent;
return; }
if (mPassive = getv<bool>(kNetworkFtpPassiveTransfers)) {
printfe("PASV");
state = passiveSent;
} else {
mReceiver.open(); FTPAddress addr(mReceiver.localAddress().defaults(localAddress()));
printfe("PORT %u,%u,%u,%u,%u,%u",
addr.h1, addr.h2, addr.h3, addr.h4, addr.p1, addr.p2);
state = portSent;
}
}
void FTPProtocol::FTPConnection::startTransfer(bool restarted)
{
if (!restarted)
if (int restartOffset = getv<int>(kNetworkRestartPosition, 0)) {
printfe("REST %d", restartOffset);
state = restartSent;
return;
}
switch (operation()) {
case download:
printfe("RETR %s", mOperationPath.c_str());
break;
case downloadDirectory:
printfe("NLST %s", mOperationPath.c_str());
break;
case downloadListing:
printfe("LIST %s", mOperationPath.c_str());
break;
case upload:
printfe("%s %s",
getv<bool>(kNetworkFtpUniqueStores, false) ? "STOU" : "STOR",
mOperationPath.c_str());
break;
default:
assert(false);
}
state = transferSent;
}
void FTPProtocol::FTPConnection::transit(Event event, char *input, size_t length)
{
if (!isDocked()) { abort(); return;
}
switch (event) {
case connectionDone: {
int error = length; observe(Observer::connectEvent, &error);
if (error) connect();
else mode(lineInput);
}
return;
case inputAvailable:
{
restarting(false);
observe(Observer::protocolReceive, input);
if (replyContinuation(input))
return; InetReply reply(input); if (!reply.valid()) fail(input);
if (replyContinuation(reply))
return;
switch (state) {
case loginInitial:
switch (reply) {
case 220:
{
string username = getv<string>(kNetworkGenericUsername,
hostTarget.haveUserPass() ? hostTarget.username() : "anonymous");
if (transfer().protocol.isProxy()) {
char portPart[10];
sprintf(portPart, ":%d", transfer().target.host.port());
username += "@" + transfer().target.host.host().name() + portPart;
}
printfe("USER %s", username.c_str());
state = loginUserSent;
break;
}
default:
fail(input);
}
break;
case loginUserSent:
switch (reply) {
case 331:
{
string password = getv<string>(kNetworkGenericPassword,
hostTarget.haveUserPass() ? hostTarget.password() : "anonymous@nowhere.net");
printfe("PASS %s", password.c_str());
state = loginPassSent;
break;
}
case 230:
startCommand();
break;
default:
fail(input);
}
break;
case loginPassSent:
switch (reply) {
case 230:
startCommand();
break;
default:
fail(input);
}
break;
case typeCommandSent:
switch (reply) {
case 200:
startCommand();
break;
default:
fail(input);
}
break;
case passiveSent:
switch (reply) {
case 227:
{
FTPAddress addr;
if (const char *p = strchr(reply.message(), '(')) {
if (sscanf(p, "(%u,%u,%u,%u,%u,%u)",
&addr.h1, &addr.h2, &addr.h3, &addr.h4, &addr.p1, &addr.p2) != 6)
fail(input);
} else if (const char *p = strstr(reply.message(), "mode")) {
if (sscanf(p+4, "%u,%u,%u,%u,%u,%u",
&addr.h1, &addr.h2, &addr.h3, &addr.h4, &addr.p1, &addr.p2) != 6)
fail(input);
} else {
fail(input);
return;
}
mDataPath.open(addr); startTransfer();
}
break;
default:
fail(input);
}
break;
case portSent:
switch (reply) {
case 200: startTransfer();
break;
default:
fail(input);
}
break;
case restartSent:
switch (reply) {
case 350: startTransfer(true); break;
default:
fail(input);
}
break;
case transferSent:
switch (reply) {
case 150:
case 125:
transfer().ftpResponse() = input; transfer().ftpResponseCode() = reply;
if (!mPassive)
mReceiver.receive(mDataPath); observe(Observer::resultCodeReady, input);
switch (operation()) {
case download:
case downloadDirectory:
case downloadListing:
mDataPath.start(sink());
observe(Observer::downloading, input);
break;
case upload:
mDataPath.start(source());
observe(Observer::uploading, input);
break;
default:
assert(false);
}
state = transferInProgress;
break;
default: if (!mPassive)
mReceiver.close();
state = idle;
fail();
break;
}
break;
case transferInProgress:
switch (reply) {
case 226: state = idle; retain(true);
mDataPath.connectionDone();
break;
case 452:
mDataPath.close();
state = idle;
fail(input, dskFulErr);
break;
default: mDataPath.close();
state = idle;
fail(input);
break;
}
break;
case directCommandSent:
{
switch (reply.type()) {
case 2:
retain(true);
finish();
break;
default:
fail();
break;
}
state = idle;
}
break;
default:
assert(false);
}
}
break;
case endOfInput:
return restart(); default:
assert(false);
}
}
void FTPProtocol::FTPConnection::transitError(const CssmCommonError &error)
{
fail(); }
bool FTPProtocol::FTPConnection::validate()
{
assert(state == idle);
tickle();
return state == idle;
}
void FTPProtocol::FTPDataConnection::start(Sink &sink)
{
secdebug("ftp", "data connection starts download");
setup();
mode(sink);
}
void FTPProtocol::FTPDataConnection::start(Source &source)
{
secdebug("ftp", "data connection starts upload");
setup();
mode(source);
}
void FTPProtocol::FTPDataConnection::setup()
{
connection.protocol.manager.addIO(this);
mFailureStatus = noErr; mConnectionDone = false; mTransferDone = false; }
int FTPProtocol::FTPDataConnection::fileDesc() const
{
return *this;
}
void FTPProtocol::FTPDataConnection::transit(Event event, char *input, size_t length)
{
assert(event == autoReadDone || event == autoWriteDone || event == endOfInput);
secdebug("ftp", "data transfer complete");
close(); finish(); }
void FTPProtocol::FTPDataConnection::transitError(const CssmCommonError &error)
{
mFailureStatus = error.osStatus();
close(); finish(); }
void FTPProtocol::FTPDataConnection::close()
{
if (isOpen()) {
connection.protocol.manager.removeIO(this);
TCPClientSocket::close();
mTransferDone = true;
}
}
void FTPProtocol::FTPDataConnection::connectionDone()
{
mConnectionDone = true;
finish();
}
void FTPProtocol::FTPDataConnection::finish()
{
if (mFailureStatus) {
connection.fail("data transfer failed", mFailureStatus);
connection.finish();
} else if (mTransferDone && mConnectionDone) {
connection.finish();
} else if (mConnectionDone) {
secdebug("ftp", "holding for data transfer completion");
} else {
secdebug("ftp", "holding for control message");
}
}
FTPProtocol::FTPTransfer::FTPTransfer(Protocol &proto, const Target &tgt, Operation operation)
: Transfer(proto, tgt, operation, defaultFtpPort)
{ }
void FTPProtocol::FTPTransfer::start()
{
FTPConnection *connection = protocol.manager.findConnection<FTPConnection>(target);
if (connection == NULL)
connection = new FTPConnection(protocol, target);
connection->dock(this);
connection->request(target.path.c_str());
}
void FTPProtocol::FTPTransfer::abort()
{
observe(Observer::aborting);
setError("aborted");
connectionAs<FTPConnection>().abort();
}
void FTPProtocol::FTPConnection::abort()
{
close();
mDataPath.close();
fail();
}
Transfer::ResultClass FTPProtocol::FTPTransfer::resultClass() const
{
switch (state()) {
case failed:
{
InetReply reply(errorDescription().c_str());
if (reply / 10 == 53) return authorizationFailure;
if (errorDescription() == "aborted")
return abortedFailure;
return remoteFailure;
}
case finished:
return success;
default:
assert(false);
}
}
FTPProtocol::FTPAddress::FTPAddress(const IPSockAddress &sockaddr)
{
uint32 addr = sockaddr.address();
h1 = addr >> 24;
h2 = (addr >> 16) & 0xFF;
h3 = (addr >> 8) & 0xFF;
h4 = addr & 0xFF;
p1 = sockaddr.port() >> 8;
p2 = sockaddr.port() & 0xFF;
}
FTPProtocol::FTPAddress::operator IPSockAddress() const
{
assert(!(h1 & ~0xff) & !(h2 & ~0xff) & !(h3 & ~0xff) & !(h4 & ~0xff)
& !(p1 & ~0xff) & !(p2 & ~0xff));
return IPSockAddress(IPAddress(h1 << 24 | h2 << 16 | h3 << 8 | h4), p1 << 8 | p2);
}
} }