]> git.saurik.com Git - apple/security.git/blob - Network/ftp-protocol.cpp
Security-176.tar.gz
[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 case 125:
336 transfer().ftpResponse() = input; // remember response for caller.
337 transfer().ftpResponseCode() = reply;
338 if (!mPassive)
339 mReceiver.receive(mDataPath); // accept incoming connection and stop listening
340 observe(Observer::resultCodeReady, input);
341
342 // engage the data path
343 switch (operation()) {
344 case download:
345 case downloadDirectory:
346 case downloadListing:
347 mDataPath.start(sink());
348 observe(Observer::downloading, input);
349 break;
350 case upload:
351 mDataPath.start(source());
352 observe(Observer::uploading, input);
353 break;
354 default:
355 assert(false);
356 }
357 state = transferInProgress;
358 break;
359 default: // download command failed
360 if (!mPassive)
361 mReceiver.close();
362 state = idle;
363 fail();
364 break;
365 }
366 break;
367 case transferInProgress:
368 switch (reply) {
369 case 226: // transfer complete
370 state = idle; // idle command mode
371 retain(true);
372 mDataPath.connectionDone();
373 break;
374 case 452:
375 mDataPath.close();
376 state = idle;
377 fail(input, dskFulErr);
378 break;
379 default: // transfer failed
380 // (ignore any error in mDataPath - prefer diagnostics from remote)
381 mDataPath.close();
382 state = idle;
383 fail(input);
384 break;
385 }
386 break;
387
388 case directCommandSent:
389 {
390 switch (reply.type()) {
391 case 2:
392 retain(true);
393 finish();
394 break;
395 default:
396 fail();
397 break;
398 }
399 state = idle;
400 }
401 break;
402
403 default:
404 assert(false);
405 }
406 }
407 break;
408
409 case endOfInput:
410 return restart(); // try to restart, fail if we can't (or shouldn't)
411 default:
412 assert(false);
413 }
414 }
415
416 void FTPProtocol::FTPConnection::transitError(const CssmCommonError &error)
417 {
418 //@@@ need to do much better diagnostics here
419 fail(); // fail transfer and discard connection
420 }
421
422
423 bool FTPProtocol::FTPConnection::validate()
424 {
425 assert(state == idle);
426 tickle();
427 return state == idle;
428 }
429
430
431 //
432 // The data connection object
433 //
434 void FTPProtocol::FTPDataConnection::start(Sink &sink)
435 {
436 secdebug("ftp", "data connection starts download");
437 setup();
438 mode(sink);
439 }
440
441 void FTPProtocol::FTPDataConnection::start(Source &source)
442 {
443 secdebug("ftp", "data connection starts upload");
444 setup();
445 mode(source);
446 }
447
448 void FTPProtocol::FTPDataConnection::setup()
449 {
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
454 }
455
456 int FTPProtocol::FTPDataConnection::fileDesc() const
457 {
458 return *this;
459 }
460
461 void FTPProtocol::FTPDataConnection::transit(Event event, char *input, size_t length)
462 {
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
467 }
468
469 void FTPProtocol::FTPDataConnection::transitError(const CssmCommonError &error)
470 {
471 mFailureStatus = error.osStatus();
472 close(); // close data path
473 finish(); // proceed with state protocol
474 }
475
476 void FTPProtocol::FTPDataConnection::close()
477 {
478 if (isOpen()) {
479 connection.protocol.manager.removeIO(this);
480 TCPClientSocket::close();
481 mTransferDone = true;
482 }
483 }
484
485 void FTPProtocol::FTPDataConnection::connectionDone()
486 {
487 mConnectionDone = true;
488 finish();
489 }
490
491 void FTPProtocol::FTPDataConnection::finish()
492 {
493 if (mFailureStatus) {
494 connection.fail("data transfer failed", mFailureStatus);
495 connection.finish();
496 } else if (mTransferDone && mConnectionDone) {
497 connection.finish();
498 } else if (mConnectionDone) {
499 secdebug("ftp", "holding for data transfer completion");
500 } else {
501 secdebug("ftp", "holding for control message");
502 }
503 }
504
505
506 //
507 // Transfer objects
508 //
509 FTPProtocol::FTPTransfer::FTPTransfer(Protocol &proto, const Target &tgt, Operation operation)
510 : Transfer(proto, tgt, operation, defaultFtpPort)
511 { }
512
513 void FTPProtocol::FTPTransfer::start()
514 {
515 FTPConnection *connection = protocol.manager.findConnection<FTPConnection>(target);
516 if (connection == NULL)
517 connection = new FTPConnection(protocol, target);
518
519 connection->dock(this);
520 connection->request(target.path.c_str());
521 }
522
523 void FTPProtocol::FTPTransfer::abort()
524 {
525 observe(Observer::aborting);
526 setError("aborted");
527 connectionAs<FTPConnection>().abort();
528 }
529
530 void FTPProtocol::FTPConnection::abort()
531 {
532 close();
533 mDataPath.close();
534 fail();
535 }
536
537
538 Transfer::ResultClass FTPProtocol::FTPTransfer::resultClass() const
539 {
540 switch (state()) {
541 case failed:
542 {
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;
550 }
551 case finished:
552 return success;
553 default:
554 assert(false);
555 }
556 }
557
558
559 //
560 // Translate the ideosyncratic text form of FTP's socket addresses to and from the real thing
561 //
562 FTPProtocol::FTPAddress::FTPAddress(const IPSockAddress &sockaddr)
563 {
564 uint32 addr = sockaddr.address();
565 h1 = addr >> 24;
566 h2 = (addr >> 16) & 0xFF;
567 h3 = (addr >> 8) & 0xFF;
568 h4 = addr & 0xFF;
569 p1 = sockaddr.port() >> 8;
570 p2 = sockaddr.port() & 0xFF;
571 }
572
573 FTPProtocol::FTPAddress::operator IPSockAddress() const
574 {
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);
578 }
579
580
581 } // end namespace Network
582 } // end namespace Security