2 * Copyright (c) 2000-2001 Apple Computer, Inc. All Rights Reserved.
4 * The contents of this file constitute Original Code as defined in and are
5 * subject to the Apple Public Source License Version 1.2 (the 'License').
6 * You may not use this file except in compliance with the License. Please obtain
7 * a copy of the License at http://www.apple.com/publicsource and read it before
10 * This Original Code and all software distributed under the License are
11 * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESS
12 * OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, INCLUDING WITHOUT
13 * LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
14 * PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. Please see the License for the
15 * specific language governing rights and limitations under the License.
20 // ftp-protocol - FTP protocol objects
22 // Basic design notes:
23 // FTPConnection and FTPDataConnection are mildly incestuous. An FTPConnection
24 // *contains* an FTPDataConnection to manage its data channel during transfers.
25 // It could *be* an FTPDataConnection, but they are both TransferEngine::TCPClients,
26 // which would make coding awkward and mistake prone.
27 // During wrap-up of a transfer, the control and data channels must synchronize to
28 // make sure they're both done. (Note that 226/250 replies do NOT guarantee that all
29 // data has been received on the data path; network latency can hold back that data
30 // for an arbitrarily long time (modulo TCP timeouts). Synchronization is achieved in
31 // classic ping-pong fashion: FTPConnection calls FTPDataConnection::connectionDone()
32 // to signal that it's side is done. The data connection calls FTPConnection::finish once
33 // it knows they're both done (because FTPConnection told it about its side already).
35 // This version has support for simple FTP proxy operation, where the PASS argument
36 // is of the form user@remote-host. FTPProxyProtocol uses this support to implement
39 // Limits on functionality:
40 // Only stream mode is supported.
43 #include <CoreServices/../Frameworks/CarbonCore.framework/Headers/MacErrors.h>
45 #include "ftp-protocol.h"
46 #include "netparameters.h"
53 // Construct the protocol object
55 FTPProtocol::FTPProtocol(Manager
&mgr
) : Protocol(mgr
, "ftp")
61 // Create a Transfer object for our protocol
63 FTPProtocol::FTPTransfer
*FTPProtocol::makeTransfer(const Target
&target
, Operation operation
)
65 return new FTPTransfer(*this, target
, operation
);
70 // Construct an FTPConnection object
72 FTPProtocol::FTPConnection::FTPConnection(Protocol
&proto
, const HostTarget
&hostTarget
)
73 : TCPConnection(proto
, hostTarget
), state(errorState
), mImageMode(false),
76 const HostTarget
&host
= proxyHostTarget();
77 connect(host
.host(), host
.port());
83 // Issue a request on the connection.
85 void FTPProtocol::FTPConnection::request(const char *path
)
88 mOperationPath
= path
;
90 if (state
== idle
) // already (idly) at command prompt, so...
91 startCommand(); // ... start operation right now
94 void FTPProtocol::FTPConnection::startCommand()
96 // notify any observer of the change in status
97 observe(Observer::resourceFound
);
99 switch (operation()) {
101 printfe("MKD %s", mOperationPath
.c_str());
102 state
= directCommandSent
;
104 case removeDirectory
:
105 printfe("RMD %s", mOperationPath
.c_str());
106 state
= directCommandSent
;
109 printfe("DELE %s", mOperationPath
.c_str());
110 state
= directCommandSent
;
113 printfe("%s", mOperationPath
.c_str());
114 state
= directCommandSent
;
118 // all other commands initiate data transfers. First, set appropriate mode
120 switch (operation()) {
121 case downloadDirectory
:
122 case downloadListing
:
123 wantImageMode
= false;
127 wantImageMode
= getv
<string
>(kNetworkFtpTransferMode
, "I") == "I";
133 // adjust transfer mode if needed
134 if (mImageMode
!= wantImageMode
) {
135 printfe("TYPE %s", wantImageMode
? "I" : "A");
136 mImageMode
= wantImageMode
; // a bit premature, but this shouldn't fail
137 state
= typeCommandSent
;
138 return; // we'll be back here
140 if (mPassive
= getv
<bool>(kNetworkFtpPassiveTransfers
)) {
141 // initiate passive mode download
145 // initiate "active mode" (default mode) download.
146 // The cooking recipe for the host/port address is deliciously subtle. We obviously take
147 // the receiver's bound port. But in most cases, its address at this stage (passive bound)
148 // is ANY, and thus useless to the server. We pick the command connection's local
149 // address for completion. However, in SOME cases mReceiver.localAddress() has
150 // a meaningful value (SOCKS, for one), so we allow this to prevail if available.
151 mReceiver
.open(); // open receiver and bind
152 FTPAddress
addr(mReceiver
.localAddress().defaults(localAddress()));
153 printfe("PORT %u,%u,%u,%u,%u,%u",
154 addr
.h1
, addr
.h2
, addr
.h3
, addr
.h4
, addr
.p1
, addr
.p2
);
161 // Initiate a data transfer (any direction or form) as indicated by mOperation.
162 // mDataPath has already been set up.
164 void FTPProtocol::FTPConnection::startTransfer(bool restarted
)
167 if (int restartOffset
= getv
<int>(kNetworkRestartPosition
, 0)) {
168 // restart requested - insert a REST command here
169 printfe("REST %d", restartOffset
);
174 switch (operation()) {
176 printfe("RETR %s", mOperationPath
.c_str());
178 case downloadDirectory
:
179 printfe("NLST %s", mOperationPath
.c_str());
181 case downloadListing
:
182 printfe("LIST %s", mOperationPath
.c_str());
186 getv
<bool>(kNetworkFtpUniqueStores
, false) ? "STOU" : "STOR",
187 mOperationPath
.c_str());
192 state
= transferSent
;
197 // This is the master state transit machine for FTP.
199 void FTPProtocol::FTPConnection::transit(Event event
, char *input
, size_t length
)
201 if (!isDocked()) { // not docked; event while in Connection cache
207 case connectionDone
: // TCP connection complete or failed
209 int error
= length
; // transmitted in the 'length' argument
210 observe(Observer::connectEvent
, &error
);
213 else // connection good
219 restarting(false); // valid input observed, commit to this Connection
221 // interpret input as FTP protocol reply, handling continued responses
222 observe(Observer::protocolReceive
, input
);
223 if (replyContinuation(input
))
224 return; // still continuing, keep reading
225 InetReply
reply(input
); // parse this reply
226 if (!reply
.valid()) // don't know why, but we're dead
228 if (replyContinuation(reply
))
229 return; // is continuation now
231 // dispatch state machine
237 string username
= getv
<string
>(kNetworkGenericUsername
,
238 hostTarget
.haveUserPass() ? hostTarget
.username() : "anonymous");
239 if (transfer().protocol
.isProxy()) {
241 sprintf(portPart
, ":%d", transfer().target
.host
.port());
242 username
+= "@" + transfer().target
.host
.host().name() + portPart
;
244 printfe("USER %s", username
.c_str());
245 state
= loginUserSent
;
256 string password
= getv
<string
>(kNetworkGenericPassword
,
257 hostTarget
.haveUserPass() ? hostTarget
.password() : "anonymous@nowhere.net");
258 printfe("PASS %s", password
.c_str());
259 state
= loginPassSent
;
278 case typeCommandSent
:
291 // reply text =~ Entering passive mode (h1,h2,h3,h4,p1,p2)
293 if (const char *p
= strchr(reply
.message(), '(')) {
294 if (sscanf(p
, "(%u,%u,%u,%u,%u,%u)",
295 &addr
.h1
, &addr
.h2
, &addr
.h3
, &addr
.h4
, &addr
.p1
, &addr
.p2
) != 6)
297 } else if (const char *p
= strstr(reply
.message(), "mode")) {
298 // RFC1123 says to be really nice to BROKEN FTP servers here
299 if (sscanf(p
+4, "%u,%u,%u,%u,%u,%u",
300 &addr
.h1
, &addr
.h2
, &addr
.h3
, &addr
.h4
, &addr
.p1
, &addr
.p2
) != 6)
306 mDataPath
.open(addr
); //@@@ synchronous - move to state machine
316 case 200: // PORT command successful
325 case 350: // Restarting at ...
326 startTransfer(true); // now do the transfer command for real
336 transfer().ftpResponse() = input
; // remember response for caller.
337 transfer().ftpResponseCode() = reply
;
339 mReceiver
.receive(mDataPath
); // accept incoming connection and stop listening
340 observe(Observer::resultCodeReady
, input
);
342 // engage the data path
343 switch (operation()) {
345 case downloadDirectory
:
346 case downloadListing
:
347 mDataPath
.start(sink());
348 observe(Observer::downloading
, input
);
351 mDataPath
.start(source());
352 observe(Observer::uploading
, input
);
357 state
= transferInProgress
;
359 default: // download command failed
367 case transferInProgress
:
369 case 226: // transfer complete
370 state
= idle
; // idle command mode
372 mDataPath
.connectionDone();
377 fail(input
, dskFulErr
);
379 default: // transfer failed
380 // (ignore any error in mDataPath - prefer diagnostics from remote)
388 case directCommandSent
:
390 switch (reply
.type()) {
410 return restart(); // try to restart, fail if we can't (or shouldn't)
416 void FTPProtocol::FTPConnection::transitError(const CssmCommonError
&error
)
418 //@@@ need to do much better diagnostics here
419 fail(); // fail transfer and discard connection
423 bool FTPProtocol::FTPConnection::validate()
425 assert(state
== idle
);
427 return state
== idle
;
432 // The data connection object
434 void FTPProtocol::FTPDataConnection::start(Sink
&sink
)
436 secdebug("ftp", "data connection starts download");
441 void FTPProtocol::FTPDataConnection::start(Source
&source
)
443 secdebug("ftp", "data connection starts upload");
448 void FTPProtocol::FTPDataConnection::setup()
450 connection
.protocol
.manager
.addIO(this);
451 mFailureStatus
= noErr
; // okay so far
452 mConnectionDone
= false; // connection side not ready yet
453 mTransferDone
= false; // our side not ready net
456 int FTPProtocol::FTPDataConnection::fileDesc() const
461 void FTPProtocol::FTPDataConnection::transit(Event event
, char *input
, size_t length
)
463 assert(event
== autoReadDone
|| event
== autoWriteDone
|| event
== endOfInput
);
464 secdebug("ftp", "data transfer complete");
465 close(); // close data path
466 finish(); // proceed with state protocol
469 void FTPProtocol::FTPDataConnection::transitError(const CssmCommonError
&error
)
471 mFailureStatus
= error
.osStatus();
472 close(); // close data path
473 finish(); // proceed with state protocol
476 void FTPProtocol::FTPDataConnection::close()
479 connection
.protocol
.manager
.removeIO(this);
480 TCPClientSocket::close();
481 mTransferDone
= true;
485 void FTPProtocol::FTPDataConnection::connectionDone()
487 mConnectionDone
= true;
491 void FTPProtocol::FTPDataConnection::finish()
493 if (mFailureStatus
) {
494 connection
.fail("data transfer failed", mFailureStatus
);
496 } else if (mTransferDone
&& mConnectionDone
) {
498 } else if (mConnectionDone
) {
499 secdebug("ftp", "holding for data transfer completion");
501 secdebug("ftp", "holding for control message");
509 FTPProtocol::FTPTransfer::FTPTransfer(Protocol
&proto
, const Target
&tgt
, Operation operation
)
510 : Transfer(proto
, tgt
, operation
, defaultFtpPort
)
513 void FTPProtocol::FTPTransfer::start()
515 FTPConnection
*connection
= protocol
.manager
.findConnection
<FTPConnection
>(target
);
516 if (connection
== NULL
)
517 connection
= new FTPConnection(protocol
, target
);
519 connection
->dock(this);
520 connection
->request(target
.path
.c_str());
523 void FTPProtocol::FTPTransfer::abort()
525 observe(Observer::aborting
);
527 connectionAs
<FTPConnection
>().abort();
530 void FTPProtocol::FTPConnection::abort()
538 Transfer::ResultClass
FTPProtocol::FTPTransfer::resultClass() const
543 InetReply
reply(errorDescription().c_str());
544 if (reply
/ 10 == 53) // 53x - authentication failure
545 return authorizationFailure
;
546 if (errorDescription() == "aborted")
547 return abortedFailure
;
548 // when in doubt, blame the remote
549 return remoteFailure
;
560 // Translate the ideosyncratic text form of FTP's socket addresses to and from the real thing
562 FTPProtocol::FTPAddress::FTPAddress(const IPSockAddress
&sockaddr
)
564 uint32 addr
= sockaddr
.address();
566 h2
= (addr
>> 16) & 0xFF;
567 h3
= (addr
>> 8) & 0xFF;
569 p1
= sockaddr
.port() >> 8;
570 p2
= sockaddr
.port() & 0xFF;
573 FTPProtocol::FTPAddress::operator IPSockAddress() const
575 assert(!(h1
& ~0xff) & !(h2
& ~0xff) & !(h3
& ~0xff) & !(h4
& ~0xff)
576 & !(p1
& ~0xff) & !(p2
& ~0xff));
577 return IPSockAddress(IPAddress(h1
<< 24 | h2
<< 16 | h3
<< 8 | h4
), p1
<< 8 | p2
);
581 } // end namespace Network
582 } // end namespace Security