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 mReceiver
.receive(mDataPath
); // accept incoming connection and stop listening
338 // engage the data path
339 switch (operation()) {
341 case downloadDirectory
:
342 case downloadListing
:
343 mDataPath
.start(sink());
346 mDataPath
.start(source());
351 state
= transferInProgress
;
353 default: // download command failed
361 case transferInProgress
:
363 case 226: // transfer complete
364 state
= idle
; // idle command mode
366 mDataPath
.connectionDone();
371 fail(input
, dskFulErr
);
373 default: // transfer failed
374 // (ignore any error in mDataPath - prefer diagnostics from remote)
382 case directCommandSent
:
384 switch (reply
.type()) {
404 return restart(); // try to restart, fail if we can't (or shouldn't)
410 void FTPProtocol::FTPConnection::transitError(const CssmCommonError
&error
)
412 //@@@ need to do much better diagnostics here
413 fail(); // fail transfer and discard connection
417 bool FTPProtocol::FTPConnection::validate()
419 assert(state
== idle
);
421 return state
== idle
;
426 // The data connection object
428 void FTPProtocol::FTPDataConnection::start(Sink
&sink
)
430 debug("ftp", "data connection starts download");
435 void FTPProtocol::FTPDataConnection::start(Source
&source
)
437 debug("ftp", "data connection starts upload");
442 void FTPProtocol::FTPDataConnection::setup()
444 connection
.protocol
.manager
.addIO(this);
445 mFailureStatus
= noErr
; // okay so far
446 mConnectionDone
= false; // connection side not ready yet
447 mTransferDone
= false; // our side not ready net
450 int FTPProtocol::FTPDataConnection::fileDesc() const
455 void FTPProtocol::FTPDataConnection::transit(Event event
, char *input
, size_t length
)
457 assert(event
== autoReadDone
|| event
== autoWriteDone
|| event
== endOfInput
);
458 debug("ftp", "data transfer complete");
459 close(); // close data path
460 finish(); // proceed with state protocol
463 void FTPProtocol::FTPDataConnection::transitError(const CssmCommonError
&error
)
465 mFailureStatus
= error
.osStatus();
466 close(); // close data path
467 finish(); // proceed with state protocol
470 void FTPProtocol::FTPDataConnection::close()
473 connection
.protocol
.manager
.removeIO(this);
474 TCPClientSocket::close();
475 mTransferDone
= true;
479 void FTPProtocol::FTPDataConnection::connectionDone()
481 mConnectionDone
= true;
485 void FTPProtocol::FTPDataConnection::finish()
487 if (mFailureStatus
) {
488 connection
.fail("data transfer failed", mFailureStatus
);
490 } else if (mTransferDone
&& mConnectionDone
) {
492 } else if (mConnectionDone
) {
493 debug("ftp", "holding for data transfer completion");
495 debug("ftp", "holding for control message");
503 FTPProtocol::FTPTransfer::FTPTransfer(Protocol
&proto
, const Target
&tgt
, Operation operation
)
504 : Transfer(proto
, tgt
, operation
, defaultFtpPort
)
507 void FTPProtocol::FTPTransfer::start()
509 FTPConnection
*connection
= protocol
.manager
.findConnection
<FTPConnection
>(target
);
510 if (connection
== NULL
)
511 connection
= new FTPConnection(protocol
, target
);
513 connection
->dock(this);
514 connection
->request(target
.path
.c_str());
517 void FTPProtocol::FTPTransfer::abort()
519 observe(Observer::aborting
);
521 connectionAs
<FTPConnection
>().abort();
524 void FTPProtocol::FTPConnection::abort()
532 Transfer::ResultClass
FTPProtocol::FTPTransfer::resultClass() const
537 InetReply
reply(errorDescription().c_str());
538 if (reply
/ 10 == 53) // 53x - authentication failure
539 return authorizationFailure
;
540 if (errorDescription() == "aborted")
541 return abortedFailure
;
542 // when in doubt, blame the remote
543 return remoteFailure
;
554 // Translate the ideosyncratic text form of FTP's socket addresses to and from the real thing
556 FTPProtocol::FTPAddress::FTPAddress(const IPSockAddress
&sockaddr
)
558 uint32 addr
= sockaddr
.address();
560 h2
= (addr
>> 16) & 0xFF;
561 h3
= (addr
>> 8) & 0xFF;
563 p1
= sockaddr
.port() >> 8;
564 p2
= sockaddr
.port() & 0xFF;
567 FTPProtocol::FTPAddress::operator IPSockAddress() const
569 assert(!(h1
& ~0xff) & !(h2
& ~0xff) & !(h3
& ~0xff) & !(h4
& ~0xff)
570 & !(p1
& ~0xff) & !(p2
& ~0xff));
571 return IPSockAddress(IPAddress(h1
<< 24 | h2
<< 16 | h3
<< 8 | h4
), p1
<< 8 | p2
);
575 } // end namespace Network
576 } // end namespace Security