]> git.saurik.com Git - apple/security.git/blob - Network/ftp-protocol.cpp
cef9777b2e83e40d985417aba693b08913f4b834
[apple/security.git] / Network / ftp-protocol.cpp
1 /*
2 * Copyright (c) 2000-2001 Apple Computer, Inc. All Rights Reserved.
3 *
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
8 * using this file.
9 *
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.
16 */
17
18
19 //
20 // ftp-protocol - FTP protocol objects
21 //
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).
34 //
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
37 // FTP/FTP proxies.
38 //
39 // Limits on functionality:
40 // Only stream mode is supported.
41 // No EBCDIC support.
42 //
43 #include <CoreServices/../Frameworks/CarbonCore.framework/Headers/MacErrors.h>
44
45 #include "ftp-protocol.h"
46 #include "netparameters.h"
47
48 namespace Security {
49 namespace Network {
50
51
52 //
53 // Construct the protocol object
54 //
55 FTPProtocol::FTPProtocol(Manager &mgr) : Protocol(mgr, "ftp")
56 {
57 }
58
59
60 //
61 // Create a Transfer object for our protocol
62 //
63 FTPProtocol::FTPTransfer *FTPProtocol::makeTransfer(const Target &target, Operation operation)
64 {
65 return new FTPTransfer(*this, target, operation);
66 }
67
68
69 //
70 // Construct an FTPConnection object
71 //
72 FTPProtocol::FTPConnection::FTPConnection(Protocol &proto, const HostTarget &hostTarget)
73 : TCPConnection(proto, hostTarget), state(errorState), mImageMode(false),
74 mDataPath(*this)
75 {
76 const HostTarget &host = proxyHostTarget();
77 connect(host.host(), host.port());
78 state = loginInitial;
79 }
80
81
82 //
83 // Issue a request on the connection.
84 //
85 void FTPProtocol::FTPConnection::request(const char *path)
86 {
87 assert(isDocked());
88 mOperationPath = path;
89
90 if (state == idle) // already (idly) at command prompt, so...
91 startCommand(); // ... start operation right now
92 }
93
94 void FTPProtocol::FTPConnection::startCommand()
95 {
96 // notify any observer of the change in status
97 observe(Observer::resourceFound);
98
99 switch (operation()) {
100 case makeDirectory:
101 printfe("MKD %s", mOperationPath.c_str());
102 state = directCommandSent;
103 return;
104 case removeDirectory:
105 printfe("RMD %s", mOperationPath.c_str());
106 state = directCommandSent;
107 return;
108 case removeFile:
109 printfe("DELE %s", mOperationPath.c_str());
110 state = directCommandSent;
111 return;
112 case genericCommand:
113 printfe("%s", mOperationPath.c_str());
114 state = directCommandSent;
115 return;
116 }
117
118 // all other commands initiate data transfers. First, set appropriate mode
119 bool wantImageMode;
120 switch (operation()) {
121 case downloadDirectory:
122 case downloadListing:
123 wantImageMode = false;
124 break;
125 case download:
126 case upload:
127 wantImageMode = getv<string>(kNetworkFtpTransferMode, "I") == "I";
128 break;
129 default:
130 assert(false);
131 }
132
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
139 }
140 if (mPassive = getv<bool>(kNetworkFtpPassiveTransfers)) {
141 // initiate passive mode download
142 printfe("PASV");
143 state = passiveSent;
144 } else {
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);
155 state = portSent;
156 }
157 }
158
159
160 //
161 // Initiate a data transfer (any direction or form) as indicated by mOperation.
162 // mDataPath has already been set up.
163 //
164 void FTPProtocol::FTPConnection::startTransfer(bool restarted)
165 {
166 if (!restarted)
167 if (int restartOffset = getv<int>(kNetworkRestartPosition, 0)) {
168 // restart requested - insert a REST command here
169 printfe("REST %d", restartOffset);
170 state = restartSent;
171 return;
172 }
173
174 switch (operation()) {
175 case download:
176 printfe("RETR %s", mOperationPath.c_str());
177 break;
178 case downloadDirectory:
179 printfe("NLST %s", mOperationPath.c_str());
180 break;
181 case downloadListing:
182 printfe("LIST %s", mOperationPath.c_str());
183 break;
184 case upload:
185 printfe("%s %s",
186 getv<bool>(kNetworkFtpUniqueStores, false) ? "STOU" : "STOR",
187 mOperationPath.c_str());
188 break;
189 default:
190 assert(false);
191 }
192 state = transferSent;
193 }
194
195
196 //
197 // This is the master state transit machine for FTP.
198 //
199 void FTPProtocol::FTPConnection::transit(Event event, char *input, size_t length)
200 {
201 if (!isDocked()) { // not docked; event while in Connection cache
202 abort(); // clean up
203 return;
204 }
205
206 switch (event) {
207 case connectionDone: // TCP connection complete or failed
208 {
209 int error = length; // transmitted in the 'length' argument
210 observe(Observer::connectEvent, &error);
211 if (error) // retry
212 connect();
213 else // connection good
214 mode(lineInput);
215 }
216 return;
217 case inputAvailable:
218 {
219 restarting(false); // valid input observed, commit to this Connection
220
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
227 fail(input);
228 if (replyContinuation(reply))
229 return; // is continuation now
230
231 // dispatch state machine
232 switch (state) {
233 case loginInitial:
234 switch (reply) {
235 case 220:
236 {
237 string username = getv<string>(kNetworkGenericUsername,
238 hostTarget.haveUserPass() ? hostTarget.username() : "anonymous");
239 if (transfer().protocol.isProxy()) {
240 char portPart[10];
241 sprintf(portPart, ":%d", transfer().target.host.port());
242 username += "@" + transfer().target.host.host().name() + portPart;
243 }
244 printfe("USER %s", username.c_str());
245 state = loginUserSent;
246 break;
247 }
248 default:
249 fail(input);
250 }
251 break;
252 case loginUserSent:
253 switch (reply) {
254 case 331:
255 {
256 string password = getv<string>(kNetworkGenericPassword,
257 hostTarget.haveUserPass() ? hostTarget.password() : "anonymous@nowhere.net");
258 printfe("PASS %s", password.c_str());
259 state = loginPassSent;
260 break;
261 }
262 case 230:
263 startCommand();
264 break;
265 default:
266 fail(input);
267 }
268 break;
269 case loginPassSent:
270 switch (reply) {
271 case 230:
272 startCommand();
273 break;
274 default:
275 fail(input);
276 }
277 break;
278 case typeCommandSent:
279 switch (reply) {
280 case 200:
281 startCommand();
282 break;
283 default:
284 fail(input);
285 }
286 break;
287 case passiveSent:
288 switch (reply) {
289 case 227:
290 {
291 // reply text =~ Entering passive mode (h1,h2,h3,h4,p1,p2)
292 FTPAddress addr;
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)
296 fail(input);
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)
301 fail(input);
302 } else {
303 fail(input);
304 return;
305 }
306 mDataPath.open(addr); //@@@ synchronous - move to state machine
307 startTransfer();
308 }
309 break;
310 default:
311 fail(input);
312 }
313 break;
314 case portSent:
315 switch (reply) {
316 case 200: // PORT command successful
317 startTransfer();
318 break;
319 default:
320 fail(input);
321 }
322 break;
323 case restartSent:
324 switch (reply) {
325 case 350: // Restarting at ...
326 startTransfer(true); // now do the transfer command for real
327 break;
328 default:
329 fail(input);
330 }
331 break;
332 case transferSent:
333 switch (reply) {
334 case 150:
335 if (!mPassive)
336 mReceiver.receive(mDataPath); // accept incoming connection and stop listening
337
338 // engage the data path
339 switch (operation()) {
340 case download:
341 case downloadDirectory:
342 case downloadListing:
343 mDataPath.start(sink());
344 break;
345 case upload:
346 mDataPath.start(source());
347 break;
348 default:
349 assert(false);
350 }
351 state = transferInProgress;
352 break;
353 default: // download command failed
354 if (!mPassive)
355 mReceiver.close();
356 state = idle;
357 fail();
358 break;
359 }
360 break;
361 case transferInProgress:
362 switch (reply) {
363 case 226: // transfer complete
364 state = idle; // idle command mode
365 retain(true);
366 mDataPath.connectionDone();
367 break;
368 case 452:
369 mDataPath.close();
370 state = idle;
371 fail(input, dskFulErr);
372 break;
373 default: // transfer failed
374 // (ignore any error in mDataPath - prefer diagnostics from remote)
375 mDataPath.close();
376 state = idle;
377 fail(input);
378 break;
379 }
380 break;
381
382 case directCommandSent:
383 {
384 switch (reply.type()) {
385 case 2:
386 retain(true);
387 finish();
388 break;
389 default:
390 fail();
391 break;
392 }
393 state = idle;
394 }
395 break;
396
397 default:
398 assert(false);
399 }
400 }
401 break;
402
403 case endOfInput:
404 return restart(); // try to restart, fail if we can't (or shouldn't)
405 default:
406 assert(false);
407 }
408 }
409
410 void FTPProtocol::FTPConnection::transitError(const CssmCommonError &error)
411 {
412 //@@@ need to do much better diagnostics here
413 fail(); // fail transfer and discard connection
414 }
415
416
417 bool FTPProtocol::FTPConnection::validate()
418 {
419 assert(state == idle);
420 tickle();
421 return state == idle;
422 }
423
424
425 //
426 // The data connection object
427 //
428 void FTPProtocol::FTPDataConnection::start(Sink &sink)
429 {
430 debug("ftp", "data connection starts download");
431 setup();
432 mode(sink);
433 }
434
435 void FTPProtocol::FTPDataConnection::start(Source &source)
436 {
437 debug("ftp", "data connection starts upload");
438 setup();
439 mode(source);
440 }
441
442 void FTPProtocol::FTPDataConnection::setup()
443 {
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
448 }
449
450 int FTPProtocol::FTPDataConnection::fileDesc() const
451 {
452 return *this;
453 }
454
455 void FTPProtocol::FTPDataConnection::transit(Event event, char *input, size_t length)
456 {
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
461 }
462
463 void FTPProtocol::FTPDataConnection::transitError(const CssmCommonError &error)
464 {
465 mFailureStatus = error.osStatus();
466 close(); // close data path
467 finish(); // proceed with state protocol
468 }
469
470 void FTPProtocol::FTPDataConnection::close()
471 {
472 if (isOpen()) {
473 connection.protocol.manager.removeIO(this);
474 TCPClientSocket::close();
475 mTransferDone = true;
476 }
477 }
478
479 void FTPProtocol::FTPDataConnection::connectionDone()
480 {
481 mConnectionDone = true;
482 finish();
483 }
484
485 void FTPProtocol::FTPDataConnection::finish()
486 {
487 if (mFailureStatus) {
488 connection.fail("data transfer failed", mFailureStatus);
489 connection.finish();
490 } else if (mTransferDone && mConnectionDone) {
491 connection.finish();
492 } else if (mConnectionDone) {
493 debug("ftp", "holding for data transfer completion");
494 } else {
495 debug("ftp", "holding for control message");
496 }
497 }
498
499
500 //
501 // Transfer objects
502 //
503 FTPProtocol::FTPTransfer::FTPTransfer(Protocol &proto, const Target &tgt, Operation operation)
504 : Transfer(proto, tgt, operation, defaultFtpPort)
505 { }
506
507 void FTPProtocol::FTPTransfer::start()
508 {
509 FTPConnection *connection = protocol.manager.findConnection<FTPConnection>(target);
510 if (connection == NULL)
511 connection = new FTPConnection(protocol, target);
512
513 connection->dock(this);
514 connection->request(target.path.c_str());
515 }
516
517 void FTPProtocol::FTPTransfer::abort()
518 {
519 observe(Observer::aborting);
520 setError("aborted");
521 connectionAs<FTPConnection>().abort();
522 }
523
524 void FTPProtocol::FTPConnection::abort()
525 {
526 close();
527 mDataPath.close();
528 fail();
529 }
530
531
532 Transfer::ResultClass FTPProtocol::FTPTransfer::resultClass() const
533 {
534 switch (state()) {
535 case failed:
536 {
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;
544 }
545 case finished:
546 return success;
547 default:
548 assert(false);
549 }
550 }
551
552
553 //
554 // Translate the ideosyncratic text form of FTP's socket addresses to and from the real thing
555 //
556 FTPProtocol::FTPAddress::FTPAddress(const IPSockAddress &sockaddr)
557 {
558 uint32 addr = sockaddr.address();
559 h1 = addr >> 24;
560 h2 = (addr >> 16) & 0xFF;
561 h3 = (addr >> 8) & 0xFF;
562 h4 = addr & 0xFF;
563 p1 = sockaddr.port() >> 8;
564 p2 = sockaddr.port() & 0xFF;
565 }
566
567 FTPProtocol::FTPAddress::operator IPSockAddress() const
568 {
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);
572 }
573
574
575 } // end namespace Network
576 } // end namespace Security