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 "ftp-protocol.h"
44 #include "netparameters.h"
52 // Construct the protocol object
54 FTPProtocol::FTPProtocol(Manager
&mgr
) : Protocol(mgr
, "ftp")
60 // Create a Transfer object for our protocol
62 FTPProtocol::FTPTransfer
*FTPProtocol::makeTransfer(const Target
&target
, Operation operation
)
64 return new FTPTransfer(*this, target
, operation
);
69 // Construct an FTPConnection object
71 FTPProtocol::FTPConnection::FTPConnection(Protocol
&proto
, const HostTarget
&hostTarget
)
72 : TCPConnection(proto
, hostTarget
), state(errorState
), mImageMode(false),
75 const HostTarget
&host
= proxyHostTarget();
76 connect(host
.host(), host
.port());
82 // Issue a request on the connection.
84 void FTPProtocol::FTPConnection::request(const char *path
)
87 mOperationPath
= path
;
89 if (state
== idle
) // already (idly) at command prompt, so...
90 startCommand(); // ... start operation right now
93 void FTPProtocol::FTPConnection::startCommand()
95 // notify any observer of the change in status
96 observe(Observer::resourceFound
);
98 switch (operation()) {
100 printfe("MKD %s", mOperationPath
.c_str());
101 state
= directCommandSent
;
103 case removeDirectory
:
104 printfe("RMD %s", mOperationPath
.c_str());
105 state
= directCommandSent
;
108 printfe("%s", mOperationPath
.c_str());
109 state
= directCommandSent
;
113 // all other commands initiate data transfers. First, set appropriate mode
115 switch (operation()) {
116 case downloadDirectory
:
117 case downloadListing
:
118 wantImageMode
= false;
122 wantImageMode
= getv
<string
>(kNetworkFtpTransferMode
, "I") == "I";
128 // adjust transfer mode if needed
129 if (mImageMode
!= wantImageMode
) {
130 printfe("TYPE %s", wantImageMode
? "I" : "A");
131 mImageMode
= wantImageMode
; // a bit premature, but this shouldn't fail
132 state
= typeCommandSent
;
133 return; // we'll be back here
135 if (mPassive
= getv
<bool>(kNetworkFtpPassiveTransfers
)) {
136 // initiate passive mode download
140 // initiate "active mode" (default mode) download.
141 // The cooking recipe for the host/port address is deliciously subtle. We obviously take
142 // the receiver's bound port. But in most cases, its address at this stage (passive bound)
143 // is ANY, and thus useless to the server. We pick the command connection's local
144 // address for completion. However, in SOME cases mReceiver.localAddress() has
145 // a meaningful value (SOCKS, for one), so we allow this to prevail if available.
146 mReceiver
.open(); // open receiver and bind
147 FTPAddress
addr(mReceiver
.localAddress().defaults(localAddress()));
148 printfe("PORT %u,%u,%u,%u,%u,%u",
149 addr
.h1
, addr
.h2
, addr
.h3
, addr
.h4
, addr
.p1
, addr
.p2
);
156 // Initiate a data transfer (any direction or form) as indicated by mOperation.
157 // mDataPath has already been set up.
159 void FTPProtocol::FTPConnection::startTransfer(bool restarted
)
162 if (int restartOffset
= getv
<int>(kNetworkRestartPosition
, 0)) {
163 // restart requested - insert a REST command here
164 printfe("REST %d", restartOffset
);
169 switch (operation()) {
171 printfe("RETR %s", mOperationPath
.c_str());
173 case downloadDirectory
:
174 printfe("NLST %s", mOperationPath
.c_str());
176 case downloadListing
:
177 printfe("LIST %s", mOperationPath
.c_str());
181 getv
<bool>(kNetworkFtpUniqueStores
, false) ? "STOU" : "STOR",
182 mOperationPath
.c_str());
187 state
= transferSent
;
192 // This is the master state transit machine for FTP.
194 void FTPProtocol::FTPConnection::transit(Event event
, char *input
, size_t length
)
196 if (!isDocked()) { // not docked; event while in Connection cache
202 case connectionDone
: // TCP connection complete or failed
204 int error
= length
; // transmitted in the 'length' argument
205 observe(Observer::connectEvent
, &error
);
208 else // connection good
214 restarting(false); // valid input observed, commit to this Connection
216 // interpret input as FTP protocol reply, handling continued responses
217 observe(Observer::protocolReceive
, input
);
218 if (replyContinuation(input
))
219 return; // still continuing, keep reading
220 InetReply
reply(input
); // parse this reply
221 if (!reply
.valid()) // don't know why, but we're dead
223 if (replyContinuation(reply
))
224 return; // is continuation now
226 // dispatch state machine
232 string username
= getv
<string
>(kNetworkGenericUsername
,
233 hostTarget
.haveUserPass() ? hostTarget
.username() : "anonymous");
234 if (transfer().protocol
.isProxy()) {
236 sprintf(portPart
, ":%d", transfer().target
.host
.port());
237 username
+= "@" + transfer().target
.host
.host().name() + portPart
;
239 printfe("USER %s", username
.c_str());
240 state
= loginUserSent
;
251 string password
= getv
<string
>(kNetworkGenericPassword
,
252 hostTarget
.haveUserPass() ? hostTarget
.password() : "anonymous@nowhere.net");
253 printfe("PASS %s", password
.c_str());
254 state
= loginPassSent
;
273 case typeCommandSent
:
286 // reply text =~ Entering passive mode (h1,h2,h3,h4,p1,p2)
288 if (const char *p
= strchr(reply
.message(), '(')) {
289 if (sscanf(p
, "(%u,%u,%u,%u,%u,%u)",
290 &addr
.h1
, &addr
.h2
, &addr
.h3
, &addr
.h4
, &addr
.p1
, &addr
.p2
) != 6)
292 } else if (const char *p
= strstr(reply
.message(), "mode")) {
293 // RFC1123 says to be really nice to BROKEN FTP servers here
294 if (sscanf(p
+4, "%u,%u,%u,%u,%u,%u",
295 &addr
.h1
, &addr
.h2
, &addr
.h3
, &addr
.h4
, &addr
.p1
, &addr
.p2
) != 6)
301 mDataPath
.open(addr
); //@@@ synchronous - move to state machine
311 case 200: // PORT command successful
320 case 350: // Restarting at ...
321 startTransfer(true); // now do the transfer command for real
331 mReceiver
.receive(mDataPath
); // accept incoming connection and stop listening
333 // engage the data path
334 switch (operation()) {
336 case downloadDirectory
:
337 case downloadListing
:
338 mDataPath
.start(sink());
341 mDataPath
.start(source());
346 state
= transferInProgress
;
348 default: // download command failed
356 case transferInProgress
:
358 case 226: // transfer complete
359 state
= idle
; // idle command mode
361 mDataPath
.connectionDone();
366 default: // transfer failed
367 // (ignore any error in mDataPath - prefer diagnostics from remote)
375 case directCommandSent
:
377 switch (reply
.type()) {
397 return restart(); // try to restart, fail if we can't (or shouldn't)
403 void FTPProtocol::FTPConnection::transitError(const CssmCommonError
&error
)
405 //@@@ need to do much better diagnostics here
406 fail(); // fail transfer and discard connection
410 bool FTPProtocol::FTPConnection::validate()
412 assert(state
== idle
);
414 return state
== idle
;
419 // The data connection object
421 void FTPProtocol::FTPDataConnection::start(Sink
&sink
)
423 debug("ftp", "data connection starts download");
428 void FTPProtocol::FTPDataConnection::start(Source
&source
)
430 debug("ftp", "data connection starts upload");
435 void FTPProtocol::FTPDataConnection::setup()
437 connection
.protocol
.manager
.addIO(this);
438 mFailureStatus
= noErr
; // okay so far
439 mConnectionDone
= false; // connection side not ready yet
440 mTransferDone
= false; // our side not ready net
443 int FTPProtocol::FTPDataConnection::fileDesc() const
448 void FTPProtocol::FTPDataConnection::transit(Event event
, char *input
, size_t length
)
450 assert(event
== autoReadDone
|| event
== autoWriteDone
|| event
== endOfInput
);
451 debug("ftp", "data transfer complete");
452 close(); // close data path
453 finish(); // proceed with state protocol
456 void FTPProtocol::FTPDataConnection::transitError(const CssmCommonError
&error
)
458 mFailureStatus
= error
.osStatus();
459 close(); // close data path
460 finish(); // proceed with state protocol
463 void FTPProtocol::FTPDataConnection::close()
466 connection
.protocol
.manager
.removeIO(this);
467 TCPClientSocket::close();
468 mTransferDone
= true;
472 void FTPProtocol::FTPDataConnection::connectionDone()
474 mConnectionDone
= true;
478 void FTPProtocol::FTPDataConnection::finish()
480 if (mFailureStatus
) {
481 connection
.fail("data transfer failed", mFailureStatus
);
483 } else if (mTransferDone
&& mConnectionDone
) {
485 } else if (mConnectionDone
) {
486 debug("ftp", "holding for data transfer completion");
488 debug("ftp", "holding for control message");
496 FTPProtocol::FTPTransfer::FTPTransfer(Protocol
&proto
, const Target
&tgt
, Operation operation
)
497 : Transfer(proto
, tgt
, operation
, defaultFtpPort
)
500 void FTPProtocol::FTPTransfer::start()
502 FTPConnection
*connection
= protocol
.manager
.findConnection
<FTPConnection
>(target
);
503 if (connection
== NULL
)
504 connection
= new FTPConnection(protocol
, target
);
506 connection
->dock(this);
507 connection
->request(target
.path
.c_str());
510 void FTPProtocol::FTPTransfer::abort()
513 connectionAs
<FTPConnection
>().abort();
516 void FTPProtocol::FTPConnection::abort()
524 Transfer::ResultClass
FTPProtocol::FTPTransfer::resultClass() const
529 InetReply
reply(errorDescription().c_str());
530 if (reply
/ 10 == 53) // 53x - authentication failure
531 return authorizationFailure
;
532 // when in doubt, blame the remote
533 return remoteFailure
;
544 // Translate the ideosyncratic text form of FTP's socket addresses to and from the real thing
546 FTPProtocol::FTPAddress::FTPAddress(const IPSockAddress
&sockaddr
)
548 uint32 addr
= sockaddr
.address();
550 h2
= (addr
>> 16) & 0xFF;
551 h3
= (addr
>> 8) & 0xFF;
553 p1
= sockaddr
.port() >> 8;
554 p2
= sockaddr
.port() & 0xFF;
557 FTPProtocol::FTPAddress::operator IPSockAddress() const
559 assert(!(h1
& ~0xff) & !(h2
& ~0xff) & !(h3
& ~0xff) & !(h4
& ~0xff)
560 & !(p1
& ~0xff) & !(p2
& ~0xff));
561 return IPSockAddress(IPAddress(h1
<< 24 | h2
<< 16 | h3
<< 8 | h4
), p1
<< 8 | p2
);
565 } // end namespace Network
566 } // end namespace Security