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