]> git.ipfire.org Git - thirdparty/squid.git/blob - src/servers/FtpServer.cc
Docs: Copyright updates for 2018 (#114)
[thirdparty/squid.git] / src / servers / FtpServer.cc
1 /*
2 * Copyright (C) 1996-2018 The Squid Software Foundation and contributors
3 *
4 * Squid software is distributed under GPLv2+ license and includes
5 * contributions from numerous individuals and organizations.
6 * Please see the COPYING and CONTRIBUTORS files for details.
7 */
8
9 /* DEBUG: section 33 Transfer protocol servers */
10
11 #include "squid.h"
12 #include "acl/FilledChecklist.h"
13 #include "base/CharacterSet.h"
14 #include "base/RefCount.h"
15 #include "base/Subscription.h"
16 #include "client_side_reply.h"
17 #include "client_side_request.h"
18 #include "clientStream.h"
19 #include "comm/ConnOpener.h"
20 #include "comm/Read.h"
21 #include "comm/TcpAcceptor.h"
22 #include "comm/Write.h"
23 #include "errorpage.h"
24 #include "fd.h"
25 #include "ftp/Elements.h"
26 #include "ftp/Parsing.h"
27 #include "globals.h"
28 #include "http/one/RequestParser.h"
29 #include "http/Stream.h"
30 #include "HttpHdrCc.h"
31 #include "ip/tools.h"
32 #include "ipc/FdNotes.h"
33 #include "parser/Tokenizer.h"
34 #include "servers/forward.h"
35 #include "servers/FtpServer.h"
36 #include "SquidConfig.h"
37 #include "StatCounters.h"
38 #include "tools.h"
39
40 #include <set>
41 #include <map>
42
43 CBDATA_NAMESPACED_CLASS_INIT(Ftp, Server);
44
45 namespace Ftp
46 {
47 static void PrintReply(MemBuf &mb, const HttpReply *reply, const char *const prefix = "");
48 static bool SupportedCommand(const SBuf &name);
49 static bool CommandHasPathParameter(const SBuf &cmd);
50 };
51
52 Ftp::Server::Server(const MasterXaction::Pointer &xact):
53 AsyncJob("Ftp::Server"),
54 ConnStateData(xact),
55 master(new MasterState),
56 uri(),
57 host(),
58 gotEpsvAll(false),
59 onDataAcceptCall(),
60 dataListenConn(),
61 dataConn(),
62 uploadAvailSize(0),
63 listener(),
64 connector(),
65 reader(),
66 waitingForOrigin(false),
67 originDataDownloadAbortedOnError(false)
68 {
69 flags.readMore = false; // we need to announce ourselves first
70 *uploadBuf = 0;
71 }
72
73 Ftp::Server::~Server()
74 {
75 closeDataConnection();
76 }
77
78 int
79 Ftp::Server::pipelinePrefetchMax() const
80 {
81 return 0; // no support for concurrent FTP requests
82 }
83
84 time_t
85 Ftp::Server::idleTimeout() const
86 {
87 return Config.Timeout.ftpClientIdle;
88 }
89
90 void
91 Ftp::Server::start()
92 {
93 ConnStateData::start();
94
95 if (transparent()) {
96 char buf[MAX_IPSTRLEN];
97 clientConnection->local.toUrl(buf, MAX_IPSTRLEN);
98 host = buf;
99 calcUri(NULL);
100 debugs(33, 5, "FTP transparent URL: " << uri);
101 }
102
103 writeEarlyReply(220, "Service ready");
104 }
105
106 /// schedules another data connection read if needed
107 void
108 Ftp::Server::maybeReadUploadData()
109 {
110 if (reader != NULL)
111 return;
112
113 const size_t availSpace = sizeof(uploadBuf) - uploadAvailSize;
114 if (availSpace <= 0)
115 return;
116
117 debugs(33, 4, dataConn << ": reading FTP data...");
118
119 typedef CommCbMemFunT<Server, CommIoCbParams> Dialer;
120 reader = JobCallback(33, 5, Dialer, this, Ftp::Server::readUploadData);
121 comm_read(dataConn, uploadBuf + uploadAvailSize, availSpace,
122 reader);
123 }
124
125 /// react to the freshly parsed request
126 void
127 Ftp::Server::doProcessRequest()
128 {
129 // zero pipelinePrefetchMax() ensures that there is only parsed request
130 Must(pipeline.count() == 1);
131 Http::StreamPointer context = pipeline.front();
132 Must(context != nullptr);
133
134 ClientHttpRequest *const http = context->http;
135 assert(http != NULL);
136
137 HttpRequest *const request = http->request;
138 Must(http->storeEntry() || request);
139 const bool mayForward = !http->storeEntry() && handleRequest(request);
140
141 if (http->storeEntry() != NULL) {
142 debugs(33, 4, "got an immediate response");
143 clientSetKeepaliveFlag(http);
144 context->pullData();
145 } else if (mayForward) {
146 debugs(33, 4, "forwarding request to server side");
147 assert(http->storeEntry() == NULL);
148 clientProcessRequest(this, Http1::RequestParserPointer(), context.getRaw());
149 } else {
150 debugs(33, 4, "will resume processing later");
151 }
152 }
153
154 void
155 Ftp::Server::processParsedRequest(Http::StreamPointer &)
156 {
157 Must(pipeline.count() == 1);
158
159 // Process FTP request asynchronously to make sure FTP
160 // data connection accept callback is fired first.
161 CallJobHere(33, 4, CbcPointer<Server>(this),
162 Ftp::Server, doProcessRequest);
163 }
164
165 /// imports more upload data from the data connection
166 void
167 Ftp::Server::readUploadData(const CommIoCbParams &io)
168 {
169 debugs(33, 5, io.conn << " size " << io.size);
170 Must(reader != NULL);
171 reader = NULL;
172
173 assert(Comm::IsConnOpen(dataConn));
174 assert(io.conn->fd == dataConn->fd);
175
176 if (io.flag == Comm::OK && bodyPipe != NULL) {
177 if (io.size > 0) {
178 statCounter.client_http.kbytes_in += io.size;
179
180 char *const current_buf = uploadBuf + uploadAvailSize;
181 if (io.buf != current_buf)
182 memmove(current_buf, io.buf, io.size);
183 uploadAvailSize += io.size;
184 shovelUploadData();
185 } else if (io.size == 0) {
186 debugs(33, 5, io.conn << " closed");
187 closeDataConnection();
188 if (uploadAvailSize <= 0)
189 finishDechunkingRequest(true);
190 }
191 } else { // not Comm::Flags::OK or unexpected read
192 debugs(33, 5, io.conn << " closed");
193 closeDataConnection();
194 finishDechunkingRequest(false);
195 }
196
197 }
198
199 /// shovel upload data from the internal buffer to the body pipe if possible
200 void
201 Ftp::Server::shovelUploadData()
202 {
203 assert(bodyPipe != NULL);
204
205 debugs(33, 5, "handling FTP request data for " << clientConnection);
206 const size_t putSize = bodyPipe->putMoreData(uploadBuf,
207 uploadAvailSize);
208 if (putSize > 0) {
209 uploadAvailSize -= putSize;
210 if (uploadAvailSize > 0)
211 memmove(uploadBuf, uploadBuf + putSize, uploadAvailSize);
212 }
213
214 if (Comm::IsConnOpen(dataConn))
215 maybeReadUploadData();
216 else if (uploadAvailSize <= 0)
217 finishDechunkingRequest(true);
218 }
219
220 void
221 Ftp::Server::noteMoreBodySpaceAvailable(BodyPipe::Pointer)
222 {
223 if (!isOpen()) // if we are closing, nothing to do
224 return;
225
226 shovelUploadData();
227 }
228
229 void
230 Ftp::Server::noteBodyConsumerAborted(BodyPipe::Pointer ptr)
231 {
232 if (!isOpen()) // if we are closing, nothing to do
233 return;
234
235 ConnStateData::noteBodyConsumerAborted(ptr);
236 closeDataConnection();
237 }
238
239 /// accept a new FTP control connection and hand it to a dedicated Server
240 void
241 Ftp::Server::AcceptCtrlConnection(const CommAcceptCbParams &params)
242 {
243 MasterXaction::Pointer xact = params.xaction;
244 AnyP::PortCfgPointer s = xact->squidPort;
245
246 // NP: it is possible the port was reconfigured when the call or accept() was queued.
247
248 if (params.flag != Comm::OK) {
249 // Its possible the call was still queued when the client disconnected
250 debugs(33, 2, s->listenConn << ": FTP accept failure: " << xstrerr(params.xerrno));
251 return;
252 }
253
254 debugs(33, 4, params.conn << ": accepted");
255 fd_note(params.conn->fd, "client ftp connect");
256
257 if (s->tcp_keepalive.enabled)
258 commSetTcpKeepalive(params.conn->fd, s->tcp_keepalive.idle, s->tcp_keepalive.interval, s->tcp_keepalive.timeout);
259
260 ++incoming_sockets_accepted;
261
262 AsyncJob::Start(new Server(xact));
263 }
264
265 void
266 Ftp::StartListening()
267 {
268 for (AnyP::PortCfgPointer s = FtpPortList; s != NULL; s = s->next) {
269 if (MAXTCPLISTENPORTS == NHttpSockets) {
270 debugs(1, DBG_IMPORTANT, "Ignoring ftp_port lines exceeding the" <<
271 " limit of " << MAXTCPLISTENPORTS << " ports.");
272 break;
273 }
274
275 // direct new connections accepted by listenConn to Accept()
276 typedef CommCbFunPtrCallT<CommAcceptCbPtrFun> AcceptCall;
277 RefCount<AcceptCall> subCall = commCbCall(5, 5, "Ftp::Server::AcceptCtrlConnection",
278 CommAcceptCbPtrFun(Ftp::Server::AcceptCtrlConnection,
279 CommAcceptCbParams(NULL)));
280 clientStartListeningOn(s, subCall, Ipc::fdnFtpSocket);
281 }
282 }
283
284 void
285 Ftp::StopListening()
286 {
287 for (AnyP::PortCfgPointer s = FtpPortList; s != NULL; s = s->next) {
288 if (s->listenConn != NULL) {
289 debugs(1, DBG_IMPORTANT, "Closing FTP port " << s->listenConn->local);
290 s->listenConn->close();
291 s->listenConn = NULL;
292 }
293 }
294 }
295
296 void
297 Ftp::Server::notePeerConnection(Comm::ConnectionPointer conn)
298 {
299 // find request
300 Http::StreamPointer context = pipeline.front();
301 Must(context != nullptr);
302 ClientHttpRequest *const http = context->http;
303 Must(http != NULL);
304 HttpRequest *const request = http->request;
305 Must(request != NULL);
306 // make FTP peer connection exclusive to our request
307 pinBusyConnection(conn, request);
308 }
309
310 void
311 Ftp::Server::clientPinnedConnectionClosed(const CommCloseCbParams &io)
312 {
313 ConnStateData::clientPinnedConnectionClosed(io);
314
315 // TODO: Keep the control connection open after fixing the reset
316 // problem below
317 if (Comm::IsConnOpen(clientConnection))
318 clientConnection->close();
319
320 // TODO: If the server control connection is gone, reset state to login
321 // again. Reseting login alone is not enough: FtpRelay::sendCommand() will
322 // not re-login because FtpRelay::serverState() is not going to be
323 // fssConnected. Calling resetLogin() alone is also harmful because
324 // it does not reset correctly the client-to-squid control connection (eg
325 // respond if required with an error code, in all cases)
326 // resetLogin("control connection closure");
327 }
328
329 /// clear client and server login-related state after the old login is gone
330 void
331 Ftp::Server::resetLogin(const char *reason)
332 {
333 debugs(33, 5, "will need to re-login due to " << reason);
334 master->clientReadGreeting = false;
335 changeState(fssBegin, reason);
336 }
337
338 /// computes uri member from host and, if tracked, working dir with file name
339 void
340 Ftp::Server::calcUri(const SBuf *file)
341 {
342 // TODO: fill a class URL instead of string
343 uri = "ftp://";
344 uri.append(host);
345 if (port->ftp_track_dirs && master->workingDir.length()) {
346 if (master->workingDir[0] != '/')
347 uri.append("/", 1);
348 uri.append(master->workingDir);
349 }
350
351 if (uri[uri.length() - 1] != '/')
352 uri.append("/", 1);
353
354 if (port->ftp_track_dirs && file) {
355 static const CharacterSet Slash("/", "/");
356 Parser::Tokenizer tok(*file);
357 tok.skipAll(Slash);
358 uri.append(tok.remaining());
359 }
360 }
361
362 /// Starts waiting for a data connection. Returns listening port.
363 /// On errors, responds with an error and returns zero.
364 unsigned int
365 Ftp::Server::listenForDataConnection()
366 {
367 closeDataConnection();
368
369 Comm::ConnectionPointer conn = new Comm::Connection;
370 conn->flags = COMM_NONBLOCKING;
371 conn->local = transparent() ? port->s : clientConnection->local;
372 conn->local.port(0);
373 const char *const note = uri.c_str();
374 comm_open_listener(SOCK_STREAM, IPPROTO_TCP, conn, note);
375 if (!Comm::IsConnOpen(conn)) {
376 debugs(5, DBG_CRITICAL, "comm_open_listener failed for FTP data: " <<
377 conn->local << " error: " << errno);
378 writeCustomReply(451, "Internal error");
379 return 0;
380 }
381
382 typedef CommCbMemFunT<Server, CommAcceptCbParams> AcceptDialer;
383 typedef AsyncCallT<AcceptDialer> AcceptCall;
384 RefCount<AcceptCall> call = static_cast<AcceptCall*>(JobCallback(5, 5, AcceptDialer, this, Ftp::Server::acceptDataConnection));
385 Subscription::Pointer sub = new CallSubscription<AcceptCall>(call);
386 listener = call.getRaw();
387 dataListenConn = conn;
388 AsyncJob::Start(new Comm::TcpAcceptor(conn, note, sub));
389
390 const unsigned int listeningPort = comm_local_port(conn->fd);
391 conn->local.port(listeningPort);
392 return listeningPort;
393 }
394
395 void
396 Ftp::Server::acceptDataConnection(const CommAcceptCbParams &params)
397 {
398 if (params.flag != Comm::OK) {
399 // Its possible the call was still queued when the client disconnected
400 debugs(33, 2, dataListenConn << ": accept "
401 "failure: " << xstrerr(params.xerrno));
402 return;
403 }
404
405 debugs(33, 4, "accepted " << params.conn);
406 fd_note(params.conn->fd, "passive client ftp data");
407 ++incoming_sockets_accepted;
408
409 if (!clientConnection) {
410 debugs(33, 5, "late data connection?");
411 closeDataConnection(); // in case we are still listening
412 params.conn->close();
413 } else if (params.conn->remote != clientConnection->remote) {
414 debugs(33, 2, "rogue data conn? ctrl: " << clientConnection->remote);
415 params.conn->close();
416 // Some FTP servers close control connection here, but it may make
417 // things worse from DoS p.o.v. and no better from data stealing p.o.v.
418 } else {
419 closeDataConnection();
420 dataConn = params.conn;
421 uploadAvailSize = 0;
422 debugs(33, 7, "ready for data");
423 if (onDataAcceptCall != NULL) {
424 AsyncCall::Pointer call = onDataAcceptCall;
425 onDataAcceptCall = NULL;
426 // If we got an upload request, start reading data from the client.
427 if (master->serverState == fssHandleUploadRequest)
428 maybeReadUploadData();
429 else
430 Must(master->serverState == fssHandleDataRequest);
431 MemBuf mb;
432 mb.init();
433 mb.appendf("150 Data connection opened.\r\n");
434 Comm::Write(clientConnection, &mb, call);
435 }
436 }
437 }
438
439 void
440 Ftp::Server::closeDataConnection()
441 {
442 if (listener != NULL) {
443 listener->cancel("no longer needed");
444 listener = NULL;
445 }
446
447 if (Comm::IsConnOpen(dataListenConn)) {
448 debugs(33, 5, "FTP closing client data listen socket: " <<
449 *dataListenConn);
450 dataListenConn->close();
451 }
452 dataListenConn = NULL;
453
454 if (reader != NULL) {
455 // Comm::ReadCancel can deal with negative FDs
456 Comm::ReadCancel(dataConn->fd, reader);
457 reader = NULL;
458 }
459
460 if (Comm::IsConnOpen(dataConn)) {
461 debugs(33, 5, "FTP closing client data connection: " <<
462 *dataConn);
463 dataConn->close();
464 }
465 dataConn = NULL;
466 }
467
468 /// Writes FTP [error] response before we fully parsed the FTP request and
469 /// created the corresponding HTTP request wrapper for that FTP request.
470 void
471 Ftp::Server::writeEarlyReply(const int code, const char *msg)
472 {
473 debugs(33, 7, code << ' ' << msg);
474 assert(99 < code && code < 1000);
475
476 MemBuf mb;
477 mb.init();
478 mb.appendf("%i %s\r\n", code, msg);
479
480 typedef CommCbMemFunT<Server, CommIoCbParams> Dialer;
481 AsyncCall::Pointer call = JobCallback(33, 5, Dialer, this, Ftp::Server::wroteEarlyReply);
482 Comm::Write(clientConnection, &mb, call);
483
484 flags.readMore = false;
485
486 // TODO: Create master transaction. Log it in wroteEarlyReply().
487 }
488
489 void
490 Ftp::Server::writeReply(MemBuf &mb)
491 {
492 debugs(9, 2, "FTP Client " << clientConnection);
493 debugs(9, 2, "FTP Client REPLY:\n---------\n" << mb.buf <<
494 "\n----------");
495
496 typedef CommCbMemFunT<Server, CommIoCbParams> Dialer;
497 AsyncCall::Pointer call = JobCallback(33, 5, Dialer, this, Ftp::Server::wroteReply);
498 Comm::Write(clientConnection, &mb, call);
499 }
500
501 void
502 Ftp::Server::writeCustomReply(const int code, const char *msg, const HttpReply *reply)
503 {
504 debugs(33, 7, code << ' ' << msg);
505 assert(99 < code && code < 1000);
506
507 const bool sendDetails = reply != NULL &&
508 reply->header.has(Http::HdrType::FTP_STATUS) && reply->header.has(Http::HdrType::FTP_REASON);
509
510 MemBuf mb;
511 mb.init();
512 if (sendDetails) {
513 mb.appendf("%i-%s\r\n", code, msg);
514 mb.appendf(" Server reply:\r\n");
515 Ftp::PrintReply(mb, reply, " ");
516 mb.appendf("%i \r\n", code);
517 } else
518 mb.appendf("%i %s\r\n", code, msg);
519
520 writeReply(mb);
521 }
522
523 void
524 Ftp::Server::changeState(const ServerState newState, const char *reason)
525 {
526 if (master->serverState == newState) {
527 debugs(33, 3, "client state unchanged at " << master->serverState <<
528 " because " << reason);
529 master->serverState = newState;
530 } else {
531 debugs(33, 3, "client state was " << master->serverState <<
532 ", now " << newState << " because " << reason);
533 master->serverState = newState;
534 }
535 }
536
537 /// whether the given FTP command has a pathname parameter
538 static bool
539 Ftp::CommandHasPathParameter(const SBuf &cmd)
540 {
541 static std::set<SBuf> PathedCommands;
542 if (!PathedCommands.size()) {
543 PathedCommands.insert(cmdMlst());
544 PathedCommands.insert(cmdMlsd());
545 PathedCommands.insert(cmdStat());
546 PathedCommands.insert(cmdNlst());
547 PathedCommands.insert(cmdList());
548 PathedCommands.insert(cmdMkd());
549 PathedCommands.insert(cmdRmd());
550 PathedCommands.insert(cmdDele());
551 PathedCommands.insert(cmdRnto());
552 PathedCommands.insert(cmdRnfr());
553 PathedCommands.insert(cmdAppe());
554 PathedCommands.insert(cmdStor());
555 PathedCommands.insert(cmdRetr());
556 PathedCommands.insert(cmdSmnt());
557 PathedCommands.insert(cmdCwd());
558 }
559
560 return PathedCommands.find(cmd) != PathedCommands.end();
561 }
562
563 /// creates a context filled with an error message for a given early error
564 Http::Stream *
565 Ftp::Server::earlyError(const EarlyErrorKind eek)
566 {
567 /* Default values, to be updated by the switch statement below */
568 int scode = 421;
569 const char *reason = "Internal error";
570 const char *errUri = "error:ftp-internal-early-error";
571
572 switch (eek) {
573 case EarlyErrorKind::HugeRequest:
574 scode = 421;
575 reason = "Huge request";
576 errUri = "error:ftp-huge-request";
577 break;
578
579 case EarlyErrorKind::MissingLogin:
580 scode = 530;
581 reason = "Must login first";
582 errUri = "error:ftp-must-login-first";
583 break;
584
585 case EarlyErrorKind::MissingUsername:
586 scode = 501;
587 reason = "Missing username";
588 errUri = "error:ftp-missing-username";
589 break;
590
591 case EarlyErrorKind::MissingHost:
592 scode = 501;
593 reason = "Missing host";
594 errUri = "error:ftp-missing-host";
595 break;
596
597 case EarlyErrorKind::UnsupportedCommand:
598 scode = 502;
599 reason = "Unknown or unsupported command";
600 errUri = "error:ftp-unsupported-command";
601 break;
602
603 case EarlyErrorKind::InvalidUri:
604 scode = 501;
605 reason = "Invalid URI";
606 errUri = "error:ftp-invalid-uri";
607 break;
608
609 case EarlyErrorKind::MalformedCommand:
610 scode = 421;
611 reason = "Malformed command";
612 errUri = "error:ftp-malformed-command";
613 break;
614
615 // no default so that a compiler can check that we have covered all cases
616 }
617
618 Http::Stream *context = abortRequestParsing(errUri);
619 clientStreamNode *node = context->getClientReplyContext();
620 Must(node);
621 clientReplyContext *repContext = dynamic_cast<clientReplyContext *>(node->data.getRaw());
622 Must(repContext);
623
624 // We cannot relay FTP scode/reason via HTTP-specific ErrorState.
625 // TODO: When/if ErrorState can handle native FTP errors, use it instead.
626 HttpReply *reply = Ftp::HttpReplyWrapper(scode, reason, Http::scBadRequest, -1);
627 repContext->setReplyToReply(reply);
628 return context;
629 }
630
631 /// Parses a single FTP request on the control connection.
632 /// Returns a new Http::Stream on valid requests and all errors.
633 /// Returns NULL on incomplete requests that may still succeed given more data.
634 Http::Stream *
635 Ftp::Server::parseOneRequest()
636 {
637 flags.readMore = false; // common for all but one case below
638
639 // OWS <command> [ RWS <parameter> ] OWS LF
640
641 // InlineSpaceChars are isspace(3) or RFC 959 Section 3.1.1.5.2, except
642 // for the LF character that we must exclude here (but see FullWhiteSpace).
643 static const char * const InlineSpaceChars = " \f\r\t\v";
644 static const CharacterSet InlineSpace = CharacterSet("Ftp::Inline", InlineSpaceChars);
645 static const CharacterSet FullWhiteSpace = (InlineSpace + CharacterSet::LF).rename("Ftp::FWS");
646 static const CharacterSet CommandChars = FullWhiteSpace.complement("Ftp::Command");
647 static const CharacterSet TailChars = CharacterSet::LF.complement("Ftp::Tail");
648
649 // This set is used to ignore empty commands without allowing an attacker
650 // to keep us endlessly busy by feeding us whitespace or empty commands.
651 static const CharacterSet &LeadingSpace = FullWhiteSpace;
652
653 SBuf cmd;
654 SBuf params;
655
656 Parser::Tokenizer tok(inBuf);
657
658 (void)tok.skipAll(LeadingSpace); // leading OWS and empty commands
659 const bool parsed = tok.prefix(cmd, CommandChars); // required command
660
661 // note that the condition below will eat either RWS or trailing OWS
662 if (parsed && tok.skipAll(InlineSpace) && tok.prefix(params, TailChars)) {
663 // now params may include trailing OWS
664 // TODO: Support right-trimming using CharacterSet in Tokenizer instead
665 static const SBuf bufWhiteSpace(InlineSpaceChars);
666 params.trim(bufWhiteSpace, false, true);
667 }
668
669 // Why limit command line and parameters size? Did not we just parse them?
670 // XXX: Our good old String cannot handle very long strings.
671 const SBuf::size_type tokenMax = min(
672 static_cast<SBuf::size_type>(32*1024), // conservative
673 static_cast<SBuf::size_type>(Config.maxRequestHeaderSize));
674 if (cmd.length() > tokenMax || params.length() > tokenMax) {
675 changeState(fssError, "huge req token");
676 quitAfterError(NULL);
677 return earlyError(EarlyErrorKind::HugeRequest);
678 }
679
680 // technically, we may skip multiple NLs below, but that is OK
681 if (!parsed || !tok.skipAll(CharacterSet::LF)) { // did not find terminating LF yet
682 // we need more data, but can we buffer more?
683 if (inBuf.length() >= Config.maxRequestHeaderSize) {
684 changeState(fssError, "huge req");
685 quitAfterError(NULL);
686 return earlyError(EarlyErrorKind::HugeRequest);
687 } else {
688 flags.readMore = true;
689 debugs(33, 5, "Waiting for more, up to " <<
690 (Config.maxRequestHeaderSize - inBuf.length()));
691 return NULL;
692 }
693 }
694
695 Must(parsed && cmd.length());
696 consumeInput(tok.parsedSize()); // TODO: Would delaying optimize copying?
697
698 debugs(33, 2, ">>ftp " << cmd << (params.isEmpty() ? "" : " ") << params);
699
700 cmd.toUpper(); // this should speed up and simplify future comparisons
701
702 // interception cases do not need USER to calculate the uri
703 if (!transparent()) {
704 if (!master->clientReadGreeting) {
705 // the first command must be USER
706 if (!pinning.pinned && cmd != cmdUser())
707 return earlyError(EarlyErrorKind::MissingLogin);
708 }
709
710 // process USER request now because it sets FTP peer host name
711 if (cmd == cmdUser()) {
712 if (Http::Stream *errCtx = handleUserRequest(cmd, params))
713 return errCtx;
714 }
715 }
716
717 if (!Ftp::SupportedCommand(cmd))
718 return earlyError(EarlyErrorKind::UnsupportedCommand);
719
720 const HttpRequestMethod method =
721 cmd == cmdAppe() || cmd == cmdStor() || cmd == cmdStou() ?
722 Http::METHOD_PUT : Http::METHOD_GET;
723
724 const SBuf *path = (params.length() && CommandHasPathParameter(cmd)) ?
725 &params : NULL;
726 calcUri(path);
727 MasterXaction::Pointer mx = new MasterXaction(XactionInitiator::initClient);
728 mx->tcpClient = clientConnection;
729 HttpRequest *const request = HttpRequest::FromUrl(uri.c_str(), mx, method);
730 if (!request) {
731 debugs(33, 5, "Invalid FTP URL: " << uri);
732 uri.clear();
733 return earlyError(EarlyErrorKind::InvalidUri);
734 }
735 char *newUri = xstrdup(uri.c_str());
736
737 request->flags.ftpNative = true;
738 request->http_ver = Http::ProtocolVersion(Ftp::ProtocolVersion().major, Ftp::ProtocolVersion().minor);
739
740 // Our fake Request-URIs are not distinctive enough for caching to work
741 request->flags.cachable = false; // XXX: reset later by maybeCacheable()
742 request->flags.noCache = true;
743
744 request->header.putStr(Http::HdrType::FTP_COMMAND, cmd.c_str());
745 request->header.putStr(Http::HdrType::FTP_ARGUMENTS, params.c_str()); // may be ""
746 if (method == Http::METHOD_PUT) {
747 request->header.putStr(Http::HdrType::EXPECT, "100-continue");
748 request->header.putStr(Http::HdrType::TRANSFER_ENCODING, "chunked");
749 }
750
751 ClientHttpRequest *const http = new ClientHttpRequest(this);
752 http->request = request;
753 HTTPMSGLOCK(http->request);
754 http->req_sz = tok.parsedSize();
755 http->uri = newUri;
756
757 Http::Stream *const result =
758 new Http::Stream(clientConnection, http);
759
760 StoreIOBuffer tempBuffer;
761 tempBuffer.data = result->reqbuf;
762 tempBuffer.length = HTTP_REQBUF_SZ;
763
764 ClientStreamData newServer = new clientReplyContext(http);
765 ClientStreamData newClient = result;
766 clientStreamInit(&http->client_stream, clientGetMoreData, clientReplyDetach,
767 clientReplyStatus, newServer, clientSocketRecipient,
768 clientSocketDetach, newClient, tempBuffer);
769
770 result->flags.parsed_ok = 1;
771 return result;
772 }
773
774 void
775 Ftp::Server::handleReply(HttpReply *reply, StoreIOBuffer data)
776 {
777 // the caller guarantees that we are dealing with the current context only
778 Http::StreamPointer context = pipeline.front();
779 assert(context != nullptr);
780
781 if (context->http && context->http->al != NULL &&
782 !context->http->al->reply && reply) {
783 context->http->al->reply = reply;
784 HTTPMSGLOCK(context->http->al->reply);
785 }
786
787 static ReplyHandler handlers[] = {
788 NULL, // fssBegin
789 NULL, // fssConnected
790 &Ftp::Server::handleFeatReply, // fssHandleFeat
791 &Ftp::Server::handlePasvReply, // fssHandlePasv
792 &Ftp::Server::handlePortReply, // fssHandlePort
793 &Ftp::Server::handleDataReply, // fssHandleDataRequest
794 &Ftp::Server::handleUploadReply, // fssHandleUploadRequest
795 &Ftp::Server::handleEprtReply,// fssHandleEprt
796 &Ftp::Server::handleEpsvReply,// fssHandleEpsv
797 NULL, // fssHandleCwd
798 NULL, // fssHandlePass
799 NULL, // fssHandleCdup
800 &Ftp::Server::handleErrorReply // fssError
801 };
802 try {
803 const Server &server = dynamic_cast<const Ftp::Server&>(*context->getConn());
804 if (const ReplyHandler handler = handlers[server.master->serverState])
805 (this->*handler)(reply, data);
806 else
807 writeForwardedReply(reply);
808 } catch (const std::exception &e) {
809 callException(e);
810 throw TexcHere(e.what());
811 }
812 }
813
814 void
815 Ftp::Server::handleFeatReply(const HttpReply *reply, StoreIOBuffer)
816 {
817 if (pipeline.front()->http->request->errType != ERR_NONE) {
818 writeCustomReply(502, "Server does not support FEAT", reply);
819 return;
820 }
821
822 Must(reply);
823 HttpReply::Pointer featReply = Ftp::HttpReplyWrapper(211, "End", Http::scNoContent, 0);
824 HttpHeader const &serverReplyHeader = reply->header;
825
826 HttpHeaderPos pos = HttpHeaderInitPos;
827 bool hasEPRT = false;
828 bool hasEPSV = false;
829 int prependSpaces = 1;
830
831 featReply->header.putStr(Http::HdrType::FTP_PRE, "\"211-Features:\"");
832 const int scode = serverReplyHeader.getInt(Http::HdrType::FTP_STATUS);
833 if (scode == 211) {
834 while (const HttpHeaderEntry *e = serverReplyHeader.getEntry(&pos)) {
835 if (e->id == Http::HdrType::FTP_PRE) {
836 // assume RFC 2389 FEAT response format, quoted by Squid:
837 // <"> SP NAME [SP PARAMS] <">
838 // but accommodate MS servers sending four SPs before NAME
839
840 // command name ends with (SP parameter) or quote
841 static const CharacterSet AfterFeatNameChars("AfterFeatName", " \"");
842 static const CharacterSet FeatNameChars = AfterFeatNameChars.complement("FeatName");
843
844 Parser::Tokenizer tok(SBuf(e->value.termedBuf()));
845 if (!tok.skip('"') || !tok.skip(' '))
846 continue;
847
848 // optional spaces; remember their number to accomodate MS servers
849 prependSpaces = 1 + tok.skipAll(CharacterSet::SP);
850
851 SBuf cmd;
852 if (!tok.prefix(cmd, FeatNameChars))
853 continue;
854 cmd.toUpper();
855
856 if (Ftp::SupportedCommand(cmd)) {
857 featReply->header.addEntry(e->clone());
858 }
859
860 if (cmd == cmdEprt())
861 hasEPRT = true;
862 else if (cmd == cmdEpsv())
863 hasEPSV = true;
864 }
865 }
866 } // else we got a FEAT error and will only report Squid-supported features
867
868 char buf[256];
869 if (!hasEPRT) {
870 snprintf(buf, sizeof(buf), "\"%*s\"", prependSpaces + 4, "EPRT");
871 featReply->header.putStr(Http::HdrType::FTP_PRE, buf);
872 }
873 if (!hasEPSV) {
874 snprintf(buf, sizeof(buf), "\"%*s\"", prependSpaces + 4, "EPSV");
875 featReply->header.putStr(Http::HdrType::FTP_PRE, buf);
876 }
877
878 featReply->header.refreshMask();
879
880 writeForwardedReply(featReply.getRaw());
881 }
882
883 void
884 Ftp::Server::handlePasvReply(const HttpReply *reply, StoreIOBuffer)
885 {
886 const Http::StreamPointer context(pipeline.front());
887 assert(context != nullptr);
888
889 if (context->http->request->errType != ERR_NONE) {
890 writeCustomReply(502, "Server does not support PASV", reply);
891 return;
892 }
893
894 const unsigned short localPort = listenForDataConnection();
895 if (!localPort)
896 return;
897
898 char addr[MAX_IPSTRLEN];
899 // remote server in interception setups and local address otherwise
900 const Ip::Address &server = transparent() ?
901 clientConnection->local : dataListenConn->local;
902 server.toStr(addr, MAX_IPSTRLEN, AF_INET);
903 addr[MAX_IPSTRLEN - 1] = '\0';
904 for (char *c = addr; *c != '\0'; ++c) {
905 if (*c == '.')
906 *c = ',';
907 }
908
909 // In interception setups, we combine remote server address with a
910 // local port number and hope that traffic will be redirected to us.
911 // Do not use "227 =a,b,c,d,p1,p2" format or omit parens: some nf_ct_ftp
912 // versions block responses that use those alternative syntax rules!
913 MemBuf mb;
914 mb.init();
915 mb.appendf("227 Entering Passive Mode (%s,%i,%i).\r\n",
916 addr,
917 static_cast<int>(localPort / 256),
918 static_cast<int>(localPort % 256));
919 debugs(9, 3, Raw("writing", mb.buf, mb.size));
920 writeReply(mb);
921 }
922
923 void
924 Ftp::Server::handlePortReply(const HttpReply *reply, StoreIOBuffer)
925 {
926 if (pipeline.front()->http->request->errType != ERR_NONE) {
927 writeCustomReply(502, "Server does not support PASV (converted from PORT)", reply);
928 return;
929 }
930
931 writeCustomReply(200, "PORT successfully converted to PASV.");
932
933 // and wait for RETR
934 }
935
936 void
937 Ftp::Server::handleErrorReply(const HttpReply *reply, StoreIOBuffer)
938 {
939 if (!pinning.pinned) // we failed to connect to server
940 uri.clear();
941 // 421: we will close due to fssError
942 writeErrorReply(reply, 421);
943 }
944
945 void
946 Ftp::Server::handleDataReply(const HttpReply *reply, StoreIOBuffer data)
947 {
948 if (reply != NULL && reply->sline.status() != Http::scOkay) {
949 writeForwardedReply(reply);
950 if (Comm::IsConnOpen(dataConn)) {
951 debugs(33, 3, "closing " << dataConn << " on KO reply");
952 closeDataConnection();
953 }
954 return;
955 }
956
957 if (!dataConn) {
958 // We got STREAM_COMPLETE (or error) and closed the client data conn.
959 debugs(33, 3, "ignoring FTP srv data response after clt data closure");
960 return;
961 }
962
963 if (!checkDataConnPost()) {
964 writeCustomReply(425, "Data connection is not established.");
965 closeDataConnection();
966 return;
967 }
968
969 debugs(33, 7, data.length);
970
971 if (data.length <= 0) {
972 replyDataWritingCheckpoint(); // skip the actual write call
973 return;
974 }
975
976 MemBuf mb;
977 mb.init(data.length + 1, data.length + 1);
978 mb.append(data.data, data.length);
979
980 typedef CommCbMemFunT<Server, CommIoCbParams> Dialer;
981 AsyncCall::Pointer call = JobCallback(33, 5, Dialer, this, Ftp::Server::wroteReplyData);
982 Comm::Write(dataConn, &mb, call);
983
984 pipeline.front()->noteSentBodyBytes(data.length);
985 }
986
987 /// called when we are done writing a chunk of the response data
988 void
989 Ftp::Server::wroteReplyData(const CommIoCbParams &io)
990 {
991 if (io.flag == Comm::ERR_CLOSING)
992 return;
993
994 if (io.flag != Comm::OK) {
995 debugs(33, 3, "FTP reply data writing failed: " << xstrerr(io.xerrno));
996 userDataCompletionCheckpoint(426);
997 return;
998 }
999
1000 assert(pipeline.front()->http);
1001 pipeline.front()->http->out.size += io.size;
1002 replyDataWritingCheckpoint();
1003 }
1004
1005 /// ClientStream checks after (actual or skipped) reply data writing
1006 void
1007 Ftp::Server::replyDataWritingCheckpoint()
1008 {
1009 switch (pipeline.front()->socketState()) {
1010 case STREAM_NONE:
1011 debugs(33, 3, "Keep going");
1012 pipeline.front()->pullData();
1013 return;
1014 case STREAM_COMPLETE:
1015 debugs(33, 3, "FTP reply data transfer successfully complete");
1016 userDataCompletionCheckpoint(226);
1017 break;
1018 case STREAM_UNPLANNED_COMPLETE:
1019 debugs(33, 3, "FTP reply data transfer failed: STREAM_UNPLANNED_COMPLETE");
1020 userDataCompletionCheckpoint(451);
1021 break;
1022 case STREAM_FAILED:
1023 userDataCompletionCheckpoint(451);
1024 debugs(33, 3, "FTP reply data transfer failed: STREAM_FAILED");
1025 break;
1026 default:
1027 fatal("unreachable code");
1028 }
1029 }
1030
1031 void
1032 Ftp::Server::handleUploadReply(const HttpReply *reply, StoreIOBuffer)
1033 {
1034 writeForwardedReply(reply);
1035 // note that the client data connection may already be closed by now
1036 }
1037
1038 void
1039 Ftp::Server::writeForwardedReply(const HttpReply *reply)
1040 {
1041 Must(reply);
1042
1043 if (waitingForOrigin) {
1044 Must(delayedReply == NULL);
1045 delayedReply = reply;
1046 return;
1047 }
1048
1049 const HttpHeader &header = reply->header;
1050 // adaptation and forwarding errors lack Http::HdrType::FTP_STATUS
1051 if (!header.has(Http::HdrType::FTP_STATUS)) {
1052 writeForwardedForeign(reply); // will get to Ftp::Server::wroteReply
1053 return;
1054 }
1055
1056 typedef CommCbMemFunT<Server, CommIoCbParams> Dialer;
1057 AsyncCall::Pointer call = JobCallback(33, 5, Dialer, this, Ftp::Server::wroteReply);
1058 writeForwardedReplyAndCall(reply, call);
1059 }
1060
1061 void
1062 Ftp::Server::handleEprtReply(const HttpReply *reply, StoreIOBuffer)
1063 {
1064 if (pipeline.front()->http->request->errType != ERR_NONE) {
1065 writeCustomReply(502, "Server does not support PASV (converted from EPRT)", reply);
1066 return;
1067 }
1068
1069 writeCustomReply(200, "EPRT successfully converted to PASV.");
1070
1071 // and wait for RETR
1072 }
1073
1074 void
1075 Ftp::Server::handleEpsvReply(const HttpReply *reply, StoreIOBuffer)
1076 {
1077 if (pipeline.front()->http->request->errType != ERR_NONE) {
1078 writeCustomReply(502, "Cannot connect to server", reply);
1079 return;
1080 }
1081
1082 const unsigned short localPort = listenForDataConnection();
1083 if (!localPort)
1084 return;
1085
1086 // In interception setups, we use a local port number and hope that data
1087 // traffic will be redirected to us.
1088 MemBuf mb;
1089 mb.init();
1090 mb.appendf("229 Entering Extended Passive Mode (|||%u|)\r\n", localPort);
1091
1092 debugs(9, 3, Raw("writing", mb.buf, mb.size));
1093 writeReply(mb);
1094 }
1095
1096 /// writes FTP error response with given status and reply-derived error details
1097 void
1098 Ftp::Server::writeErrorReply(const HttpReply *reply, const int scode)
1099 {
1100 const HttpRequest *request = pipeline.front()->http->request;
1101 assert(request);
1102
1103 MemBuf mb;
1104 mb.init();
1105
1106 if (request->errType != ERR_NONE)
1107 mb.appendf("%i-%s\r\n", scode, errorPageName(request->errType));
1108
1109 if (request->errDetail > 0) {
1110 // XXX: > 0 may not always mean that this is an errno
1111 mb.appendf("%i-Error: (%d) %s\r\n", scode,
1112 request->errDetail,
1113 strerror(request->errDetail));
1114 }
1115
1116 #if USE_ADAPTATION
1117 // XXX: Remove hard coded names. Use an error page template instead.
1118 const Adaptation::History::Pointer ah = request->adaptHistory();
1119 if (ah != NULL) { // XXX: add adapt::<all_h but use lastMeta here
1120 const String info = ah->allMeta.getByName("X-Response-Info");
1121 const String desc = ah->allMeta.getByName("X-Response-Desc");
1122 if (info.size())
1123 mb.appendf("%i-Information: %s\r\n", scode, info.termedBuf());
1124 if (desc.size())
1125 mb.appendf("%i-Description: %s\r\n", scode, desc.termedBuf());
1126 }
1127 #endif
1128
1129 const char *reason = "Lost Error";
1130 if (reply) {
1131 reason = reply->header.has(Http::HdrType::FTP_REASON) ?
1132 reply->header.getStr(Http::HdrType::FTP_REASON):
1133 reply->sline.reason();
1134 }
1135
1136 mb.appendf("%i %s\r\n", scode, reason); // error terminating line
1137
1138 // TODO: errorpage.cc should detect FTP client and use
1139 // configurable FTP-friendly error templates which we should
1140 // write to the client "as is" instead of hiding most of the info
1141
1142 writeReply(mb);
1143 }
1144
1145 /// writes FTP response based on HTTP reply that is not an FTP-response wrapper
1146 /// for example, internally-generated Squid "errorpages" end up here (for now)
1147 void
1148 Ftp::Server::writeForwardedForeign(const HttpReply *reply)
1149 {
1150 changeState(fssConnected, "foreign reply");
1151 closeDataConnection();
1152 // 451: We intend to keep the control connection open.
1153 writeErrorReply(reply, 451);
1154 }
1155
1156 bool
1157 Ftp::Server::writeControlMsgAndCall(HttpReply *reply, AsyncCall::Pointer &call)
1158 {
1159 // the caller guarantees that we are dealing with the current context only
1160 // the caller should also make sure reply->header.has(Http::HdrType::FTP_STATUS)
1161 writeForwardedReplyAndCall(reply, call);
1162 return true;
1163 }
1164
1165 void
1166 Ftp::Server::writeForwardedReplyAndCall(const HttpReply *reply, AsyncCall::Pointer &call)
1167 {
1168 assert(reply != NULL);
1169 const HttpHeader &header = reply->header;
1170
1171 // without status, the caller must use the writeForwardedForeign() path
1172 Must(header.has(Http::HdrType::FTP_STATUS));
1173 Must(header.has(Http::HdrType::FTP_REASON));
1174 const int scode = header.getInt(Http::HdrType::FTP_STATUS);
1175 debugs(33, 7, "scode: " << scode);
1176
1177 // Status 125 or 150 implies upload or data request, but we still check
1178 // the state in case the server is buggy.
1179 if ((scode == 125 || scode == 150) &&
1180 (master->serverState == fssHandleUploadRequest ||
1181 master->serverState == fssHandleDataRequest)) {
1182 if (checkDataConnPost()) {
1183 // If the data connection is ready, start reading data (here)
1184 // and forward the response to client (further below).
1185 debugs(33, 7, "data connection established, start data transfer");
1186 if (master->serverState == fssHandleUploadRequest)
1187 maybeReadUploadData();
1188 } else {
1189 // If we are waiting to accept the data connection, keep waiting.
1190 if (Comm::IsConnOpen(dataListenConn)) {
1191 debugs(33, 7, "wait for the client to establish a data connection");
1192 onDataAcceptCall = call;
1193 // TODO: Add connect timeout for passive connections listener?
1194 // TODO: Remember server response so that we can forward it?
1195 } else {
1196 // Either the connection was establised and closed after the
1197 // data was transferred OR we failed to establish an active
1198 // data connection and already sent the error to the client.
1199 // In either case, there is nothing more to do.
1200 debugs(33, 7, "done with data OR active connection failed");
1201 }
1202 return;
1203 }
1204 }
1205
1206 MemBuf mb;
1207 mb.init();
1208 Ftp::PrintReply(mb, reply);
1209
1210 debugs(9, 2, "FTP Client " << clientConnection);
1211 debugs(9, 2, "FTP Client REPLY:\n---------\n" << mb.buf <<
1212 "\n----------");
1213
1214 Comm::Write(clientConnection, &mb, call);
1215 }
1216
1217 static void
1218 Ftp::PrintReply(MemBuf &mb, const HttpReply *reply, const char *const)
1219 {
1220 const HttpHeader &header = reply->header;
1221
1222 HttpHeaderPos pos = HttpHeaderInitPos;
1223 while (const HttpHeaderEntry *e = header.getEntry(&pos)) {
1224 if (e->id == Http::HdrType::FTP_PRE) {
1225 String raw;
1226 if (httpHeaderParseQuotedString(e->value.rawBuf(), e->value.size(), &raw))
1227 mb.appendf("%s\r\n", raw.termedBuf());
1228 }
1229 }
1230
1231 if (header.has(Http::HdrType::FTP_STATUS)) {
1232 const char *reason = header.getStr(Http::HdrType::FTP_REASON);
1233 mb.appendf("%i %s\r\n", header.getInt(Http::HdrType::FTP_STATUS),
1234 (reason ? reason : 0));
1235 }
1236 }
1237
1238 void
1239 Ftp::Server::wroteEarlyReply(const CommIoCbParams &io)
1240 {
1241 if (io.flag == Comm::ERR_CLOSING)
1242 return;
1243
1244 if (io.flag != Comm::OK) {
1245 debugs(33, 3, "FTP reply writing failed: " << xstrerr(io.xerrno));
1246 io.conn->close();
1247 return;
1248 }
1249
1250 Http::StreamPointer context = pipeline.front();
1251 if (context != nullptr && context->http) {
1252 context->http->out.size += io.size;
1253 context->http->out.headers_sz += io.size;
1254 }
1255
1256 flags.readMore = true;
1257 readSomeData();
1258 }
1259
1260 void
1261 Ftp::Server::wroteReply(const CommIoCbParams &io)
1262 {
1263 if (io.flag == Comm::ERR_CLOSING)
1264 return;
1265
1266 if (io.flag != Comm::OK) {
1267 debugs(33, 3, "FTP reply writing failed: " << xstrerr(io.xerrno));
1268 io.conn->close();
1269 return;
1270 }
1271
1272 Http::StreamPointer context = pipeline.front();
1273 assert(context->http);
1274 context->http->out.size += io.size;
1275 context->http->out.headers_sz += io.size;
1276
1277 if (master->serverState == fssError) {
1278 debugs(33, 5, "closing on FTP server error");
1279 io.conn->close();
1280 return;
1281 }
1282
1283 const clientStream_status_t socketState = context->socketState();
1284 debugs(33, 5, "FTP client stream state " << socketState);
1285 switch (socketState) {
1286 case STREAM_UNPLANNED_COMPLETE:
1287 case STREAM_FAILED:
1288 io.conn->close();
1289 return;
1290
1291 case STREAM_NONE:
1292 case STREAM_COMPLETE:
1293 flags.readMore = true;
1294 changeState(fssConnected, "Ftp::Server::wroteReply");
1295 if (bodyParser)
1296 finishDechunkingRequest(false);
1297 context->finished();
1298 kick();
1299 return;
1300 }
1301 }
1302
1303 bool
1304 Ftp::Server::handleRequest(HttpRequest *request)
1305 {
1306 debugs(33, 9, request);
1307 Must(request);
1308
1309 HttpHeader &header = request->header;
1310 Must(header.has(Http::HdrType::FTP_COMMAND));
1311 String &cmd = header.findEntry(Http::HdrType::FTP_COMMAND)->value;
1312 Must(header.has(Http::HdrType::FTP_ARGUMENTS));
1313 String &params = header.findEntry(Http::HdrType::FTP_ARGUMENTS)->value;
1314
1315 if (Debug::Enabled(9, 2)) {
1316 MemBuf mb;
1317 mb.init();
1318 request->pack(&mb);
1319
1320 debugs(9, 2, "FTP Client " << clientConnection);
1321 debugs(9, 2, "FTP Client REQUEST:\n---------\n" << mb.buf <<
1322 "\n----------");
1323 }
1324
1325 // TODO: When HttpHeader uses SBuf, change keys to SBuf
1326 typedef std::map<const std::string, RequestHandler> RequestHandlers;
1327 static RequestHandlers handlers;
1328 if (!handlers.size()) {
1329 handlers["LIST"] = &Ftp::Server::handleDataRequest;
1330 handlers["NLST"] = &Ftp::Server::handleDataRequest;
1331 handlers["MLSD"] = &Ftp::Server::handleDataRequest;
1332 handlers["FEAT"] = &Ftp::Server::handleFeatRequest;
1333 handlers["PASV"] = &Ftp::Server::handlePasvRequest;
1334 handlers["PORT"] = &Ftp::Server::handlePortRequest;
1335 handlers["RETR"] = &Ftp::Server::handleDataRequest;
1336 handlers["EPRT"] = &Ftp::Server::handleEprtRequest;
1337 handlers["EPSV"] = &Ftp::Server::handleEpsvRequest;
1338 handlers["CWD"] = &Ftp::Server::handleCwdRequest;
1339 handlers["PASS"] = &Ftp::Server::handlePassRequest;
1340 handlers["CDUP"] = &Ftp::Server::handleCdupRequest;
1341 }
1342
1343 RequestHandler handler = NULL;
1344 if (request->method == Http::METHOD_PUT)
1345 handler = &Ftp::Server::handleUploadRequest;
1346 else {
1347 const RequestHandlers::const_iterator hi = handlers.find(cmd.termedBuf());
1348 if (hi != handlers.end())
1349 handler = hi->second;
1350 }
1351
1352 if (!handler) {
1353 debugs(9, 7, "forwarding " << cmd << " as is, no post-processing");
1354 return true;
1355 }
1356
1357 return (this->*handler)(cmd, params);
1358 }
1359
1360 /// Called to parse USER command, which is required to create an HTTP request
1361 /// wrapper. W/o request, the errors are handled by returning earlyError().
1362 Http::Stream *
1363 Ftp::Server::handleUserRequest(const SBuf &, SBuf &params)
1364 {
1365 if (params.isEmpty())
1366 return earlyError(EarlyErrorKind::MissingUsername);
1367
1368 // find the [end of] user name
1369 const SBuf::size_type eou = params.rfind('@');
1370 if (eou == SBuf::npos || eou + 1 >= params.length())
1371 return earlyError(EarlyErrorKind::MissingHost);
1372
1373 // Determine the intended destination.
1374 host = params.substr(eou + 1, params.length());
1375 // If we can parse it as raw IPv6 address, then surround with "[]".
1376 // Otherwise (domain, IPv4, [bracketed] IPv6, garbage, etc), use as is.
1377 if (host.find(':') != SBuf::npos) {
1378 const Ip::Address ipa(host.c_str());
1379 if (!ipa.isAnyAddr()) {
1380 char ipBuf[MAX_IPSTRLEN];
1381 ipa.toHostStr(ipBuf, MAX_IPSTRLEN);
1382 host = ipBuf;
1383 }
1384 }
1385
1386 // const SBuf login = params.substr(0, eou);
1387 params.chop(0, eou); // leave just the login part for the peer
1388
1389 SBuf oldUri;
1390 if (master->clientReadGreeting)
1391 oldUri = uri;
1392
1393 master->workingDir.clear();
1394 calcUri(NULL);
1395
1396 if (!master->clientReadGreeting) {
1397 debugs(9, 3, "set URI to " << uri);
1398 } else if (oldUri.caseCmp(uri) == 0) {
1399 debugs(9, 5, "kept URI as " << oldUri);
1400 } else {
1401 debugs(9, 3, "reset URI from " << oldUri << " to " << uri);
1402 closeDataConnection();
1403 unpinConnection(true); // close control connection to peer
1404 resetLogin("URI reset");
1405 }
1406
1407 return NULL; // no early errors
1408 }
1409
1410 bool
1411 Ftp::Server::handleFeatRequest(String &, String &)
1412 {
1413 changeState(fssHandleFeat, "handleFeatRequest");
1414 return true;
1415 }
1416
1417 bool
1418 Ftp::Server::handlePasvRequest(String &, String &params)
1419 {
1420 if (gotEpsvAll) {
1421 setReply(500, "Bad PASV command");
1422 return false;
1423 }
1424
1425 if (params.size() > 0) {
1426 setReply(501, "Unexpected parameter");
1427 return false;
1428 }
1429
1430 changeState(fssHandlePasv, "handlePasvRequest");
1431 // no need to fake PASV request via setDataCommand() in true PASV case
1432 return true;
1433 }
1434
1435 /// [Re]initializes dataConn for active data transfers. Does not connect.
1436 bool
1437 Ftp::Server::createDataConnection(Ip::Address cltAddr)
1438 {
1439 assert(clientConnection != NULL);
1440 assert(!clientConnection->remote.isAnyAddr());
1441
1442 if (cltAddr != clientConnection->remote) {
1443 debugs(33, 2, "rogue PORT " << cltAddr << " request? ctrl: " << clientConnection->remote);
1444 // Closing the control connection would not help with attacks because
1445 // the client is evidently able to connect to us. Besides, closing
1446 // makes retrials easier for the client and more damaging to us.
1447 setReply(501, "Prohibited parameter value");
1448 return false;
1449 }
1450
1451 closeDataConnection();
1452
1453 Comm::ConnectionPointer conn = new Comm::Connection();
1454 conn->flags |= COMM_DOBIND;
1455
1456 if (clientConnection->flags & COMM_INTERCEPTION) {
1457 // In the case of NAT interception conn->local value is not set
1458 // because the TCP stack will automatically pick correct source
1459 // address for the data connection. We must only ensure that IP
1460 // version matches client's address.
1461 conn->local.setAnyAddr();
1462
1463 if (cltAddr.isIPv4())
1464 conn->local.setIPv4();
1465
1466 conn->remote = cltAddr;
1467 } else {
1468 // In the case of explicit-proxy the local IP of the control connection
1469 // is the Squid IP the client is knowingly talking to.
1470 //
1471 // In the case of TPROXY the IP address of the control connection is
1472 // server IP the client is connecting to, it can be spoofed by Squid.
1473 //
1474 // In both cases some clients may refuse to accept data connections if
1475 // these control connectin local-IP's are not used.
1476 conn->setAddrs(clientConnection->local, cltAddr);
1477
1478 // Using non-local addresses in TPROXY mode requires appropriate socket option.
1479 if (clientConnection->flags & COMM_TRANSPARENT)
1480 conn->flags |= COMM_TRANSPARENT;
1481 }
1482
1483 // RFC 959 requires active FTP connections to originate from port 20
1484 // but that would preclude us from supporting concurrent transfers! (XXX?)
1485 conn->local.port(0);
1486
1487 debugs(9, 3, "will actively connect from " << conn->local << " to " <<
1488 conn->remote);
1489
1490 dataConn = conn;
1491 uploadAvailSize = 0;
1492 return true;
1493 }
1494
1495 bool
1496 Ftp::Server::handlePortRequest(String &, String &params)
1497 {
1498 // TODO: Should PORT errors trigger closeDataConnection() cleanup?
1499
1500 if (gotEpsvAll) {
1501 setReply(500, "Rejecting PORT after EPSV ALL");
1502 return false;
1503 }
1504
1505 if (!params.size()) {
1506 setReply(501, "Missing parameter");
1507 return false;
1508 }
1509
1510 Ip::Address cltAddr;
1511 if (!Ftp::ParseIpPort(params.termedBuf(), NULL, cltAddr)) {
1512 setReply(501, "Invalid parameter");
1513 return false;
1514 }
1515
1516 if (!createDataConnection(cltAddr))
1517 return false;
1518
1519 changeState(fssHandlePort, "handlePortRequest");
1520 setDataCommand();
1521 return true; // forward our fake PASV request
1522 }
1523
1524 bool
1525 Ftp::Server::handleDataRequest(String &, String &)
1526 {
1527 if (!checkDataConnPre())
1528 return false;
1529
1530 master->userDataDone = 0;
1531 originDataDownloadAbortedOnError = false;
1532
1533 changeState(fssHandleDataRequest, "handleDataRequest");
1534
1535 return true;
1536 }
1537
1538 bool
1539 Ftp::Server::handleUploadRequest(String &, String &)
1540 {
1541 if (!checkDataConnPre())
1542 return false;
1543
1544 if (Config.accessList.forceRequestBodyContinuation) {
1545 ClientHttpRequest *http = pipeline.front()->http;
1546 HttpRequest *request = http->request;
1547 ACLFilledChecklist bodyContinuationCheck(Config.accessList.forceRequestBodyContinuation, request, NULL);
1548 if (bodyContinuationCheck.fastCheck().allowed()) {
1549 request->forcedBodyContinuation = true;
1550 if (checkDataConnPost()) {
1551 // Write control Msg
1552 writeEarlyReply(150, "Data connection opened");
1553 maybeReadUploadData();
1554 } else {
1555 // wait for acceptDataConnection but tell it to call wroteEarlyReply
1556 // after writing "150 Data connection opened"
1557 typedef CommCbMemFunT<Server, CommIoCbParams> Dialer;
1558 AsyncCall::Pointer call = JobCallback(33, 5, Dialer, this, Ftp::Server::wroteEarlyReply);
1559 onDataAcceptCall = call;
1560 }
1561 }
1562 }
1563
1564 changeState(fssHandleUploadRequest, "handleDataRequest");
1565
1566 return true;
1567 }
1568
1569 bool
1570 Ftp::Server::handleEprtRequest(String &, String &params)
1571 {
1572 debugs(9, 3, "Process an EPRT " << params);
1573
1574 if (gotEpsvAll) {
1575 setReply(500, "Rejecting EPRT after EPSV ALL");
1576 return false;
1577 }
1578
1579 if (!params.size()) {
1580 setReply(501, "Missing parameter");
1581 return false;
1582 }
1583
1584 Ip::Address cltAddr;
1585 if (!Ftp::ParseProtoIpPort(params.termedBuf(), cltAddr)) {
1586 setReply(501, "Invalid parameter");
1587 return false;
1588 }
1589
1590 if (!createDataConnection(cltAddr))
1591 return false;
1592
1593 changeState(fssHandleEprt, "handleEprtRequest");
1594 setDataCommand();
1595 return true; // forward our fake PASV request
1596 }
1597
1598 bool
1599 Ftp::Server::handleEpsvRequest(String &, String &params)
1600 {
1601 debugs(9, 3, "Process an EPSV command with params: " << params);
1602 if (params.size() <= 0) {
1603 // treat parameterless EPSV as "use the protocol of the ctrl conn"
1604 } else if (params.caseCmp("ALL") == 0) {
1605 setReply(200, "EPSV ALL ok");
1606 gotEpsvAll = true;
1607 return false;
1608 } else if (params.cmp("2") == 0) {
1609 if (!Ip::EnableIpv6) {
1610 setReply(522, "Network protocol not supported, use (1)");
1611 return false;
1612 }
1613 } else if (params.cmp("1") != 0) {
1614 setReply(501, "Unsupported EPSV parameter");
1615 return false;
1616 }
1617
1618 changeState(fssHandleEpsv, "handleEpsvRequest");
1619 setDataCommand();
1620 return true; // forward our fake PASV request
1621 }
1622
1623 bool
1624 Ftp::Server::handleCwdRequest(String &, String &)
1625 {
1626 changeState(fssHandleCwd, "handleCwdRequest");
1627 return true;
1628 }
1629
1630 bool
1631 Ftp::Server::handlePassRequest(String &, String &)
1632 {
1633 changeState(fssHandlePass, "handlePassRequest");
1634 return true;
1635 }
1636
1637 bool
1638 Ftp::Server::handleCdupRequest(String &, String &)
1639 {
1640 changeState(fssHandleCdup, "handleCdupRequest");
1641 return true;
1642 }
1643
1644 // Convert user PORT, EPRT, PASV, or EPSV data command to Squid PASV command.
1645 // Squid FTP client decides what data command to use with peers.
1646 void
1647 Ftp::Server::setDataCommand()
1648 {
1649 ClientHttpRequest *const http = pipeline.front()->http;
1650 assert(http != NULL);
1651 HttpRequest *const request = http->request;
1652 assert(request != NULL);
1653 HttpHeader &header = request->header;
1654 header.delById(Http::HdrType::FTP_COMMAND);
1655 header.putStr(Http::HdrType::FTP_COMMAND, "PASV");
1656 header.delById(Http::HdrType::FTP_ARGUMENTS);
1657 header.putStr(Http::HdrType::FTP_ARGUMENTS, "");
1658 debugs(9, 5, "client data command converted to fake PASV");
1659 }
1660
1661 /// check that client data connection is ready for future I/O or at least
1662 /// has a chance of becoming ready soon.
1663 bool
1664 Ftp::Server::checkDataConnPre()
1665 {
1666 if (Comm::IsConnOpen(dataConn))
1667 return true;
1668
1669 if (Comm::IsConnOpen(dataListenConn)) {
1670 // We are still waiting for a client to connect to us after PASV.
1671 // Perhaps client's data conn handshake has not reached us yet.
1672 // After we talk to the server, checkDataConnPost() will recheck.
1673 debugs(33, 3, "expecting clt data conn " << dataListenConn);
1674 return true;
1675 }
1676
1677 if (!dataConn || dataConn->remote.isAnyAddr()) {
1678 debugs(33, 5, "missing " << dataConn);
1679 // TODO: use client address and default port instead.
1680 setReply(425, "Use PORT or PASV first");
1681 return false;
1682 }
1683
1684 // active transfer: open a data connection from Squid to client
1685 typedef CommCbMemFunT<Server, CommConnectCbParams> Dialer;
1686 connector = JobCallback(17, 3, Dialer, this, Ftp::Server::connectedForData);
1687 Comm::ConnOpener *cs = new Comm::ConnOpener(dataConn, connector,
1688 Config.Timeout.connect);
1689 AsyncJob::Start(cs);
1690 return false; // ConnStateData::processFtpRequest waits handleConnectDone
1691 }
1692
1693 /// Check that client data connection is ready for immediate I/O.
1694 bool
1695 Ftp::Server::checkDataConnPost() const
1696 {
1697 if (!Comm::IsConnOpen(dataConn)) {
1698 debugs(33, 3, "missing client data conn: " << dataConn);
1699 return false;
1700 }
1701 return true;
1702 }
1703
1704 /// Done establishing a data connection to the user.
1705 void
1706 Ftp::Server::connectedForData(const CommConnectCbParams &params)
1707 {
1708 connector = NULL;
1709
1710 if (params.flag != Comm::OK) {
1711 /* it might have been a timeout with a partially open link */
1712 if (params.conn != NULL)
1713 params.conn->close();
1714 setReply(425, "Cannot open data connection.");
1715 Http::StreamPointer context = pipeline.front();
1716 Must(context->http);
1717 Must(context->http->storeEntry() != NULL);
1718 } else {
1719 Must(dataConn == params.conn);
1720 Must(Comm::IsConnOpen(params.conn));
1721 fd_note(params.conn->fd, "active client ftp data");
1722 }
1723
1724 doProcessRequest();
1725 }
1726
1727 void
1728 Ftp::Server::setReply(const int code, const char *msg)
1729 {
1730 Http::StreamPointer context = pipeline.front();
1731 ClientHttpRequest *const http = context->http;
1732 assert(http != NULL);
1733 assert(http->storeEntry() == NULL);
1734
1735 HttpReply *const reply = Ftp::HttpReplyWrapper(code, msg, Http::scNoContent, 0);
1736
1737 setLogUri(http, urlCanonicalClean(http->request));
1738
1739 clientStreamNode *const node = context->getClientReplyContext();
1740 clientReplyContext *const repContext =
1741 dynamic_cast<clientReplyContext *>(node->data.getRaw());
1742 assert(repContext != NULL);
1743
1744 RequestFlags reqFlags;
1745 reqFlags.cachable = false; // force releaseRequest() in storeCreateEntry()
1746 reqFlags.noCache = true;
1747 repContext->createStoreEntry(http->request->method, reqFlags);
1748 http->storeEntry()->replaceHttpReply(reply);
1749 }
1750
1751 void
1752 Ftp::Server::callException(const std::exception &e)
1753 {
1754 debugs(33, 2, "FTP::Server job caught: " << e.what());
1755 closeDataConnection();
1756 unpinConnection(true);
1757 if (Comm::IsConnOpen(clientConnection))
1758 clientConnection->close();
1759 AsyncJob::callException(e);
1760 }
1761
1762 void
1763 Ftp::Server::startWaitingForOrigin()
1764 {
1765 if (!isOpen()) // if we are closing, nothing to do
1766 return;
1767
1768 debugs(33, 5, "waiting for Ftp::Client data transfer to end");
1769 waitingForOrigin = true;
1770 }
1771
1772 void
1773 Ftp::Server::stopWaitingForOrigin(int originStatus)
1774 {
1775 Must(waitingForOrigin);
1776 waitingForOrigin = false;
1777
1778 if (!isOpen()) // if we are closing, nothing to do
1779 return;
1780
1781 // if we have already decided how to respond, respond now
1782 if (delayedReply) {
1783 HttpReply::Pointer reply = delayedReply;
1784 delayedReply = nullptr;
1785 writeForwardedReply(reply.getRaw());
1786 return; // do not completeDataDownload() after an earlier response
1787 }
1788
1789 if (master->serverState != fssHandleDataRequest)
1790 return;
1791
1792 // completeDataDownload() could be waitingForOrigin in fssHandleDataRequest
1793 // Depending on which side has finished downloading first, either trust
1794 // master->userDataDone status or set originDataDownloadAbortedOnError:
1795 if (master->userDataDone) {
1796 // We finished downloading before Ftp::Client. Most likely, the
1797 // adaptation shortened the origin response or we hit an error.
1798 // Our status (stored in master->userDataDone) is more informative.
1799 // Use master->userDataDone; avoid originDataDownloadAbortedOnError.
1800 completeDataDownload();
1801 } else {
1802 debugs(33, 5, "too early to write the response");
1803 // Ftp::Client naturally finished downloading before us. Set
1804 // originDataDownloadAbortedOnError to overwrite future
1805 // master->userDataDone and relay Ftp::Client error, if there was
1806 // any, to the user.
1807 originDataDownloadAbortedOnError = (originStatus >= 400);
1808 }
1809 }
1810
1811 void Ftp::Server::userDataCompletionCheckpoint(int finalStatusCode)
1812 {
1813 Must(!master->userDataDone);
1814 master->userDataDone = finalStatusCode;
1815
1816 if (bodyParser)
1817 finishDechunkingRequest(false);
1818
1819 if (waitingForOrigin) {
1820 // The completeDataDownload() is not called here unconditionally
1821 // because we want to signal the FTP user that we are not fully
1822 // done processing its data stream, even though all data bytes
1823 // have been sent or received already.
1824 debugs(33, 5, "Transfering from FTP server is not complete");
1825 return;
1826 }
1827
1828 // Adjust our reply if the server aborted with an error before we are done.
1829 if (master->userDataDone == 226 && originDataDownloadAbortedOnError) {
1830 debugs(33, 5, "Transfering from FTP server terminated with an error, adjust status code");
1831 master->userDataDone = 451;
1832 }
1833 completeDataDownload();
1834 }
1835
1836 void Ftp::Server::completeDataDownload()
1837 {
1838 writeCustomReply(master->userDataDone, master->userDataDone == 226 ? "Transfer complete" : "Server error; transfer aborted");
1839 closeDataConnection();
1840 }
1841
1842 /// Whether Squid FTP Relay supports a named feature (e.g., a command).
1843 static bool
1844 Ftp::SupportedCommand(const SBuf &name)
1845 {
1846 static std::set<SBuf> BlackList;
1847 if (BlackList.empty()) {
1848 /* Add FTP commands that Squid cannot relay correctly. */
1849
1850 // We probably do not support AUTH TLS.* and AUTH SSL,
1851 // but let's disclaim all AUTH support to KISS, for now.
1852 BlackList.insert(cmdAuth());
1853 }
1854
1855 // we claim support for all commands that we do not know about
1856 return BlackList.find(name) == BlackList.end();
1857 }
1858