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