+++ /dev/null
-/*
- * Copyright (c) 2000-2001 Apple Computer, Inc. All Rights Reserved.
- *
- * The contents of this file constitute Original Code as defined in and are
- * subject to the Apple Public Source License Version 1.2 (the 'License').
- * You may not use this file except in compliance with the License. Please obtain
- * a copy of the License at http://www.apple.com/publicsource and read it before
- * using this file.
- *
- * This Original Code and all software distributed under the License are
- * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESS
- * OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, INCLUDING WITHOUT
- * LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
- * PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. Please see the License for the
- * specific language governing rights and limitations under the License.
- */
-
-
-//
-// ftp-protocol - FTP protocol objects
-//
-// Basic design notes:
-// FTPConnection and FTPDataConnection are mildly incestuous. An FTPConnection
-// *contains* an FTPDataConnection to manage its data channel during transfers.
-// It could *be* an FTPDataConnection, but they are both TransferEngine::TCPClients,
-// which would make coding awkward and mistake prone.
-// During wrap-up of a transfer, the control and data channels must synchronize to
-// make sure they're both done. (Note that 226/250 replies do NOT guarantee that all
-// data has been received on the data path; network latency can hold back that data
-// for an arbitrarily long time (modulo TCP timeouts). Synchronization is achieved in
-// classic ping-pong fashion: FTPConnection calls FTPDataConnection::connectionDone()
-// to signal that it's side is done. The data connection calls FTPConnection::finish once
-// it knows they're both done (because FTPConnection told it about its side already).
-//
-// This version has support for simple FTP proxy operation, where the PASS argument
-// is of the form user@remote-host. FTPProxyProtocol uses this support to implement
-// FTP/FTP proxies.
-//
-// Limits on functionality:
-// Only stream mode is supported.
-// No EBCDIC support.
-//
-#include <CoreServices/../Frameworks/CarbonCore.framework/Headers/MacErrors.h>
-
-#include "ftp-protocol.h"
-#include "netparameters.h"
-
-namespace Security {
-namespace Network {
-
-
-//
-// Construct the protocol object
-//
-FTPProtocol::FTPProtocol(Manager &mgr) : Protocol(mgr, "ftp")
-{
-}
-
-
-//
-// Create a Transfer object for our protocol
-//
-FTPProtocol::FTPTransfer *FTPProtocol::makeTransfer(const Target &target, Operation operation)
-{
- return new FTPTransfer(*this, target, operation);
-}
-
-
-//
-// Construct an FTPConnection object
-//
-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;
-}
-
-
-//
-// Issue a request on the connection.
-//
-void FTPProtocol::FTPConnection::request(const char *path)
-{
- assert(isDocked());
- mOperationPath = path;
-
- if (state == idle) // already (idly) at command prompt, so...
- startCommand(); // ... start operation right now
-}
-
-void FTPProtocol::FTPConnection::startCommand()
-{
- // notify any observer of the change in status
- 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;
- }
-
- // all other commands initiate data transfers. First, set appropriate mode
- 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);
- }
-
- // adjust transfer mode if needed
- if (mImageMode != wantImageMode) {
- printfe("TYPE %s", wantImageMode ? "I" : "A");
- mImageMode = wantImageMode; // a bit premature, but this shouldn't fail
- state = typeCommandSent;
- return; // we'll be back here
- }
- if (mPassive = getv<bool>(kNetworkFtpPassiveTransfers)) {
- // initiate passive mode download
- printfe("PASV");
- state = passiveSent;
- } else {
- // initiate "active mode" (default mode) download.
- // The cooking recipe for the host/port address is deliciously subtle. We obviously take
- // the receiver's bound port. But in most cases, its address at this stage (passive bound)
- // is ANY, and thus useless to the server. We pick the command connection's local
- // address for completion. However, in SOME cases mReceiver.localAddress() has
- // a meaningful value (SOCKS, for one), so we allow this to prevail if available.
- mReceiver.open(); // open receiver and bind
- 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;
- }
-}
-
-
-//
-// Initiate a data transfer (any direction or form) as indicated by mOperation.
-// mDataPath has already been set up.
-//
-void FTPProtocol::FTPConnection::startTransfer(bool restarted)
-{
- if (!restarted)
- if (int restartOffset = getv<int>(kNetworkRestartPosition, 0)) {
- // restart requested - insert a REST command here
- 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;
-}
-
-
-//
-// This is the master state transit machine for FTP.
-//
-void FTPProtocol::FTPConnection::transit(Event event, char *input, size_t length)
-{
- if (!isDocked()) { // not docked; event while in Connection cache
- abort(); // clean up
- return;
- }
-
- switch (event) {
- case connectionDone: // TCP connection complete or failed
- {
- int error = length; // transmitted in the 'length' argument
- observe(Observer::connectEvent, &error);
- if (error) // retry
- connect();
- else // connection good
- mode(lineInput);
- }
- return;
- case inputAvailable:
- {
- restarting(false); // valid input observed, commit to this Connection
-
- // interpret input as FTP protocol reply, handling continued responses
- observe(Observer::protocolReceive, input);
- if (replyContinuation(input))
- return; // still continuing, keep reading
- InetReply reply(input); // parse this reply
- if (!reply.valid()) // don't know why, but we're dead
- fail(input);
- if (replyContinuation(reply))
- return; // is continuation now
-
- // dispatch state machine
- 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:
- {
- // reply text =~ Entering passive mode (h1,h2,h3,h4,p1,p2)
- 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")) {
- // RFC1123 says to be really nice to BROKEN FTP servers here
- 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); //@@@ synchronous - move to state machine
- startTransfer();
- }
- break;
- default:
- fail(input);
- }
- break;
- case portSent:
- switch (reply) {
- case 200: // PORT command successful
- startTransfer();
- break;
- default:
- fail(input);
- }
- break;
- case restartSent:
- switch (reply) {
- case 350: // Restarting at ...
- startTransfer(true); // now do the transfer command for real
- break;
- default:
- fail(input);
- }
- break;
- case transferSent:
- switch (reply) {
- case 150:
- case 125:
- transfer().ftpResponse() = input; // remember response for caller.
- transfer().ftpResponseCode() = reply;
- if (!mPassive)
- mReceiver.receive(mDataPath); // accept incoming connection and stop listening
- observe(Observer::resultCodeReady, input);
-
- // engage the data path
- 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: // download command failed
- if (!mPassive)
- mReceiver.close();
- state = idle;
- fail();
- break;
- }
- break;
- case transferInProgress:
- switch (reply) {
- case 226: // transfer complete
- state = idle; // idle command mode
- retain(true);
- mDataPath.connectionDone();
- break;
- case 452:
- mDataPath.close();
- state = idle;
- fail(input, dskFulErr);
- break;
- default: // transfer failed
- // (ignore any error in mDataPath - prefer diagnostics from remote)
- 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(); // try to restart, fail if we can't (or shouldn't)
- default:
- assert(false);
- }
-}
-
-void FTPProtocol::FTPConnection::transitError(const CssmCommonError &error)
-{
- //@@@ need to do much better diagnostics here
- fail(); // fail transfer and discard connection
-}
-
-
-bool FTPProtocol::FTPConnection::validate()
-{
- assert(state == idle);
- tickle();
- return state == idle;
-}
-
-
-//
-// The data connection object
-//
-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; // okay so far
- mConnectionDone = false; // connection side not ready yet
- mTransferDone = false; // our side not ready net
-}
-
-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(); // close data path
- finish(); // proceed with state protocol
-}
-
-void FTPProtocol::FTPDataConnection::transitError(const CssmCommonError &error)
-{
- mFailureStatus = error.osStatus();
- close(); // close data path
- finish(); // proceed with state protocol
-}
-
-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");
- }
-}
-
-
-//
-// Transfer objects
-//
-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) // 53x - authentication failure
- return authorizationFailure;
- if (errorDescription() == "aborted")
- return abortedFailure;
- // when in doubt, blame the remote
- return remoteFailure;
- }
- case finished:
- return success;
- default:
- assert(false);
- }
-}
-
-
-//
-// Translate the ideosyncratic text form of FTP's socket addresses to and from the real thing
-//
-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);
-}
-
-
-} // end namespace Network
-} // end namespace Security