X-Git-Url: https://git.saurik.com/apple/security.git/blobdiff_plain/ed6778a32ecff23bc2dfb6ca452badd0c68774a0..563f4f96f568bcdc0a04a82f89cafe3bebbe43f1:/Network/ftp-protocol.cpp?ds=inline diff --git a/Network/ftp-protocol.cpp b/Network/ftp-protocol.cpp deleted file mode 100644 index 4e700136..00000000 --- a/Network/ftp-protocol.cpp +++ /dev/null @@ -1,582 +0,0 @@ -/* - * 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 - -#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(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(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(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(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(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(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(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().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