]> git.ipfire.org Git - thirdparty/squid.git/blame - src/servers/FtpServer.cc
Supply ALE with HttpReply before checking http_reply_access (#398)
[thirdparty/squid.git] / src / servers / FtpServer.cc
CommitLineData
92ae4c86 1/*
f6e9a3ee 2 * Copyright (C) 1996-2019 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
6b2b6cfe 155Ftp::Server::processParsedRequest(Http::StreamPointer &)
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);
92ae4c86 306 // make FTP peer connection exclusive to our request
801cfc26 307 pinBusyConnection(conn, request);
92ae4c86
AR
308}
309
310void
311Ftp::Server::clientPinnedConnectionClosed(const CommCloseCbParams &io)
312{
313 ConnStateData::clientPinnedConnectionClosed(io);
314
92cfc72f
CT
315 // TODO: Keep the control connection open after fixing the reset
316 // problem below
317 if (Comm::IsConnOpen(clientConnection))
318 clientConnection->close();
e7ce227f 319
92cfc72f
CT
320 // TODO: If the server control connection is gone, reset state to login
321 // again. Reseting login alone is not enough: FtpRelay::sendCommand() will
51edd95f 322 // not re-login because FtpRelay::serverState() is not going to be
92cfc72f
CT
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");
92ae4c86
AR
327}
328
e7ce227f
AR
329/// clear client and server login-related state after the old login is gone
330void
331Ftp::Server::resetLogin(const char *reason)
332{
333 debugs(33, 5, "will need to re-login due to " << reason);
aea65fec 334 master->clientReadGreeting = false;
e7ce227f
AR
335 changeState(fssBegin, reason);
336}
337
92ae4c86
AR
338/// computes uri member from host and, if tracked, working dir with file name
339void
1ab04517 340Ftp::Server::calcUri(const SBuf *file)
92ae4c86 341{
c8ab5ec6 342 // TODO: fill a class AnyP::Uri instead of string
92ae4c86
AR
343 uri = "ftp://";
344 uri.append(host);
aea65fec
AR
345 if (port->ftp_track_dirs && master->workingDir.length()) {
346 if (master->workingDir[0] != '/')
ab4c5a36 347 uri.append("/", 1);
aea65fec 348 uri.append(master->workingDir);
92ae4c86
AR
349 }
350
1ab04517 351 if (uri[uri.length() - 1] != '/')
ab4c5a36 352 uri.append("/", 1);
92ae4c86
AR
353
354 if (port->ftp_track_dirs && file) {
1ab04517
AR
355 static const CharacterSet Slash("/", "/");
356 Parser::Tokenizer tok(*file);
357 tok.skipAll(Slash);
358 uri.append(tok.remaining());
92ae4c86
AR
359 }
360}
361
362/// Starts waiting for a data connection. Returns listening port.
363/// On errors, responds with an error and returns zero.
364unsigned int
365Ftp::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);
1ab04517 373 const char *const note = uri.c_str();
92ae4c86
AR
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
395void
396Ftp::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();
27c841f6 413 } else if (params.conn->remote != clientConnection->remote) {
92ae4c86
AR
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.
aea65fec 427 if (master->serverState == fssHandleUploadRequest)
92ae4c86
AR
428 maybeReadUploadData();
429 else
aea65fec 430 Must(master->serverState == fssHandleDataRequest);
92ae4c86
AR
431 MemBuf mb;
432 mb.init();
4391cd15 433 mb.appendf("150 Data connection opened.\r\n");
92ae4c86
AR
434 Comm::Write(clientConnection, &mb, call);
435 }
436 }
437}
438
439void
440Ftp::Server::closeDataConnection()
441{
442 if (listener != NULL) {
443 listener->cancel("no longer needed");
444 listener = NULL;
445 }
446
447 if (Comm::IsConnOpen(dataListenConn)) {
e7ce227f 448 debugs(33, 5, "FTP closing client data listen socket: " <<
92ae4c86
AR
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)) {
e7ce227f 461 debugs(33, 5, "FTP closing client data connection: " <<
92ae4c86
AR
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.
470void
471Ftp::Server::writeEarlyReply(const int code, const char *msg)
472{
e7ce227f 473 debugs(33, 7, code << ' ' << msg);
92ae4c86
AR
474 assert(99 < code && code < 1000);
475
476 MemBuf mb;
477 mb.init();
4391cd15 478 mb.appendf("%i %s\r\n", code, msg);
92ae4c86
AR
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
489void
490Ftp::Server::writeReply(MemBuf &mb)
491{
aea65fec
AR
492 debugs(9, 2, "FTP Client " << clientConnection);
493 debugs(9, 2, "FTP Client REPLY:\n---------\n" << mb.buf <<
92ae4c86
AR
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
501void
502Ftp::Server::writeCustomReply(const int code, const char *msg, const HttpReply *reply)
503{
e7ce227f 504 debugs(33, 7, code << ' ' << msg);
92ae4c86
AR
505 assert(99 < code && code < 1000);
506
507 const bool sendDetails = reply != NULL &&
789217a2 508 reply->header.has(Http::HdrType::FTP_STATUS) && reply->header.has(Http::HdrType::FTP_REASON);
92ae4c86
AR
509
510 MemBuf mb;
511 mb.init();
512 if (sendDetails) {
4391cd15
AJ
513 mb.appendf("%i-%s\r\n", code, msg);
514 mb.appendf(" Server reply:\r\n");
92ae4c86 515 Ftp::PrintReply(mb, reply, " ");
4391cd15 516 mb.appendf("%i \r\n", code);
92ae4c86 517 } else
4391cd15 518 mb.appendf("%i %s\r\n", code, msg);
92ae4c86
AR
519
520 writeReply(mb);
521}
522
523void
524Ftp::Server::changeState(const ServerState newState, const char *reason)
525{
aea65fec
AR
526 if (master->serverState == newState) {
527 debugs(33, 3, "client state unchanged at " << master->serverState <<
92ae4c86 528 " because " << reason);
aea65fec 529 master->serverState = newState;
92ae4c86 530 } else {
aea65fec 531 debugs(33, 3, "client state was " << master->serverState <<
92ae4c86 532 ", now " << newState << " because " << reason);
aea65fec 533 master->serverState = newState;
92ae4c86
AR
534 }
535}
536
537/// whether the given FTP command has a pathname parameter
538static bool
1ab04517
AR
539Ftp::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();
92ae4c86
AR
561}
562
eacfca83 563/// creates a context filled with an error message for a given early error
d3dddfb5 564Http::Stream *
eacfca83
AR
565Ftp::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) {
3aab028c 573 case EarlyErrorKind::HugeRequest:
eacfca83
AR
574 scode = 421;
575 reason = "Huge request";
576 errUri = "error:ftp-huge-request";
577 break;
578
3aab028c 579 case EarlyErrorKind::MissingLogin:
eacfca83
AR
580 scode = 530;
581 reason = "Must login first";
582 errUri = "error:ftp-must-login-first";
583 break;
584
3aab028c 585 case EarlyErrorKind::MissingUsername:
eacfca83
AR
586 scode = 501;
587 reason = "Missing username";
588 errUri = "error:ftp-missing-username";
589 break;
590
3aab028c 591 case EarlyErrorKind::MissingHost:
eacfca83
AR
592 scode = 501;
593 reason = "Missing host";
594 errUri = "error:ftp-missing-host";
595 break;
596
3aab028c 597 case EarlyErrorKind::UnsupportedCommand:
eacfca83
AR
598 scode = 502;
599 reason = "Unknown or unsupported command";
600 errUri = "error:ftp-unsupported-command";
601 break;
602
3aab028c 603 case EarlyErrorKind::InvalidUri:
eacfca83
AR
604 scode = 501;
605 reason = "Invalid URI";
606 errUri = "error:ftp-invalid-uri";
607 break;
608
3aab028c 609 case EarlyErrorKind::MalformedCommand:
eacfca83
AR
610 scode = 421;
611 reason = "Malformed command";
612 errUri = "error:ftp-malformed-command";
613 break;
614
74d4eef6 615 // no default so that a compiler can check that we have covered all cases
eacfca83
AR
616 }
617
d3dddfb5 618 Http::Stream *context = abortRequestParsing(errUri);
eacfca83
AR
619 clientStreamNode *node = context->getClientReplyContext();
620 Must(node);
621 clientReplyContext *repContext = dynamic_cast<clientReplyContext *>(node->data.getRaw());
3d01c5ab 622 Must(repContext);
eacfca83
AR
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
92ae4c86 631/// Parses a single FTP request on the control connection.
d3dddfb5 632/// Returns a new Http::Stream on valid requests and all errors.
eacfca83 633/// Returns NULL on incomplete requests that may still succeed given more data.
d3dddfb5 634Http::Stream *
9bafa70d 635Ftp::Server::parseOneRequest()
92ae4c86 636{
eacfca83
AR
637 flags.readMore = false; // common for all but one case below
638
1ab04517 639 // OWS <command> [ RWS <parameter> ] OWS LF
aea65fec
AR
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");
92ae4c86 648
1ab04517
AR
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.
aea65fec 651 static const CharacterSet &LeadingSpace = FullWhiteSpace;
92ae4c86 652
1ab04517
AR
653 SBuf cmd;
654 SBuf params;
92ae4c86 655
fcc444e3 656 Parser::Tokenizer tok(inBuf);
92ae4c86 657
1ab04517 658 (void)tok.skipAll(LeadingSpace); // leading OWS and empty commands
aea65fec 659 const bool parsed = tok.prefix(cmd, CommandChars); // required command
92ae4c86 660
1ab04517 661 // note that the condition below will eat either RWS or trailing OWS
aea65fec 662 if (parsed && tok.skipAll(InlineSpace) && tok.prefix(params, TailChars)) {
1ab04517
AR
663 // now params may include trailing OWS
664 // TODO: Support right-trimming using CharacterSet in Tokenizer instead
aea65fec 665 static const SBuf bufWhiteSpace(InlineSpaceChars);
1ab04517 666 params.trim(bufWhiteSpace, false, true);
92ae4c86
AR
667 }
668
eacfca83
AR
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(
74d4eef6
A
672 static_cast<SBuf::size_type>(32*1024), // conservative
673 static_cast<SBuf::size_type>(Config.maxRequestHeaderSize));
eacfca83
AR
674 if (cmd.length() > tokenMax || params.length() > tokenMax) {
675 changeState(fssError, "huge req token");
676 quitAfterError(NULL);
3aab028c 677 return earlyError(EarlyErrorKind::HugeRequest);
eacfca83
AR
678 }
679
1ab04517 680 // technically, we may skip multiple NLs below, but that is OK
aea65fec 681 if (!parsed || !tok.skipAll(CharacterSet::LF)) { // did not find terminating LF yet
1ab04517 682 // we need more data, but can we buffer more?
fcc444e3 683 if (inBuf.length() >= Config.maxRequestHeaderSize) {
1ab04517 684 changeState(fssError, "huge req");
eacfca83 685 quitAfterError(NULL);
3aab028c 686 return earlyError(EarlyErrorKind::HugeRequest);
1ab04517 687 } else {
eacfca83 688 flags.readMore = true;
1ab04517 689 debugs(33, 5, "Waiting for more, up to " <<
fcc444e3 690 (Config.maxRequestHeaderSize - inBuf.length()));
1ab04517
AR
691 return NULL;
692 }
693 }
92ae4c86 694
1ab04517
AR
695 Must(parsed && cmd.length());
696 consumeInput(tok.parsedSize()); // TODO: Would delaying optimize copying?
92ae4c86 697
1ab04517 698 debugs(33, 2, ">>ftp " << cmd << (params.isEmpty() ? "" : " ") << params);
92ae4c86 699
1ab04517 700 cmd.toUpper(); // this should speed up and simplify future comparisons
92ae4c86 701
e7ce227f
AR
702 // interception cases do not need USER to calculate the uri
703 if (!transparent()) {
aea65fec 704 if (!master->clientReadGreeting) {
e7ce227f 705 // the first command must be USER
eacfca83 706 if (!pinning.pinned && cmd != cmdUser())
3aab028c 707 return earlyError(EarlyErrorKind::MissingLogin);
92ae4c86 708 }
92ae4c86 709
e7ce227f 710 // process USER request now because it sets FTP peer host name
eacfca83 711 if (cmd == cmdUser()) {
d3dddfb5 712 if (Http::Stream *errCtx = handleUserRequest(cmd, params))
eacfca83
AR
713 return errCtx;
714 }
e7ce227f 715 }
92ae4c86 716
eacfca83 717 if (!Ftp::SupportedCommand(cmd))
3aab028c 718 return earlyError(EarlyErrorKind::UnsupportedCommand);
92ae4c86
AR
719
720 const HttpRequestMethod method =
1ab04517 721 cmd == cmdAppe() || cmd == cmdStor() || cmd == cmdStou() ?
92ae4c86
AR
722 Http::METHOD_PUT : Http::METHOD_GET;
723
aea65fec 724 const SBuf *path = (params.length() && CommandHasPathParameter(cmd)) ?
27c841f6 725 &params : NULL;
1ab04517 726 calcUri(path);
5ceaee75
CT
727 MasterXaction::Pointer mx = new MasterXaction(XactionInitiator::initClient);
728 mx->tcpClient = clientConnection;
8babada0 729 HttpRequest *const request = HttpRequest::FromUrl(uri.c_str(), mx, method);
92ae4c86 730 if (!request) {
e7ce227f 731 debugs(33, 5, "Invalid FTP URL: " << uri);
1ab04517 732 uri.clear();
3aab028c 733 return earlyError(EarlyErrorKind::InvalidUri);
92ae4c86 734 }
8babada0 735 char *newUri = xstrdup(uri.c_str());
92ae4c86
AR
736
737 request->flags.ftpNative = true;
9bafa70d 738 request->http_ver = Http::ProtocolVersion(Ftp::ProtocolVersion().major, Ftp::ProtocolVersion().minor);
92ae4c86
AR
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
789217a2
FC
744 request->header.putStr(Http::HdrType::FTP_COMMAND, cmd.c_str());
745 request->header.putStr(Http::HdrType::FTP_ARGUMENTS, params.c_str()); // may be ""
92ae4c86 746 if (method == Http::METHOD_PUT) {
789217a2
FC
747 request->header.putStr(Http::HdrType::EXPECT, "100-continue");
748 request->header.putStr(Http::HdrType::TRANSFER_ENCODING, "chunked");
92ae4c86
AR
749 }
750
751 ClientHttpRequest *const http = new ClientHttpRequest(this);
1ab04517 752 http->req_sz = tok.parsedSize();
92ae4c86 753 http->uri = newUri;
bec110e4 754 http->initRequest(request);
92ae4c86 755
d3dddfb5
AJ
756 Http::Stream *const result =
757 new Http::Stream(clientConnection, http);
92ae4c86 758
942b1c39
AJ
759 StoreIOBuffer tempBuffer;
760 tempBuffer.data = result->reqbuf;
761 tempBuffer.length = HTTP_REQBUF_SZ;
762
92ae4c86
AR
763 ClientStreamData newServer = new clientReplyContext(http);
764 ClientStreamData newClient = result;
765 clientStreamInit(&http->client_stream, clientGetMoreData, clientReplyDetach,
766 clientReplyStatus, newServer, clientSocketRecipient,
942b1c39 767 clientSocketDetach, newClient, tempBuffer);
92ae4c86 768
92ae4c86 769 result->flags.parsed_ok = 1;
92ae4c86
AR
770 return result;
771}
772
773void
774Ftp::Server::handleReply(HttpReply *reply, StoreIOBuffer data)
775{
776 // the caller guarantees that we are dealing with the current context only
d3dddfb5 777 Http::StreamPointer context = pipeline.front();
efd5e784 778 assert(context != nullptr);
92ae4c86 779
92ae4c86
AR
780 static ReplyHandler handlers[] = {
781 NULL, // fssBegin
782 NULL, // fssConnected
783 &Ftp::Server::handleFeatReply, // fssHandleFeat
784 &Ftp::Server::handlePasvReply, // fssHandlePasv
785 &Ftp::Server::handlePortReply, // fssHandlePort
786 &Ftp::Server::handleDataReply, // fssHandleDataRequest
787 &Ftp::Server::handleUploadReply, // fssHandleUploadRequest
788 &Ftp::Server::handleEprtReply,// fssHandleEprt
789 &Ftp::Server::handleEpsvReply,// fssHandleEpsv
790 NULL, // fssHandleCwd
3cc0f4e7 791 NULL, // fssHandlePass
92ae4c86
AR
792 NULL, // fssHandleCdup
793 &Ftp::Server::handleErrorReply // fssError
794 };
0d253dfa
CT
795 try {
796 const Server &server = dynamic_cast<const Ftp::Server&>(*context->getConn());
797 if (const ReplyHandler handler = handlers[server.master->serverState])
798 (this->*handler)(reply, data);
799 else
800 writeForwardedReply(reply);
801 } catch (const std::exception &e) {
802 callException(e);
803 throw TexcHere(e.what());
804 }
92ae4c86
AR
805}
806
807void
aea65fec 808Ftp::Server::handleFeatReply(const HttpReply *reply, StoreIOBuffer)
92ae4c86 809{
efd5e784 810 if (pipeline.front()->http->request->errType != ERR_NONE) {
92ae4c86
AR
811 writeCustomReply(502, "Server does not support FEAT", reply);
812 return;
813 }
814
0d253dfa 815 Must(reply);
f0174f5b
CT
816 HttpReply::Pointer featReply = Ftp::HttpReplyWrapper(211, "End", Http::scNoContent, 0);
817 HttpHeader const &serverReplyHeader = reply->header;
92ae4c86 818
92ae4c86
AR
819 HttpHeaderPos pos = HttpHeaderInitPos;
820 bool hasEPRT = false;
821 bool hasEPSV = false;
822 int prependSpaces = 1;
f0174f5b 823
789217a2
FC
824 featReply->header.putStr(Http::HdrType::FTP_PRE, "\"211-Features:\"");
825 const int scode = serverReplyHeader.getInt(Http::HdrType::FTP_STATUS);
f0174f5b
CT
826 if (scode == 211) {
827 while (const HttpHeaderEntry *e = serverReplyHeader.getEntry(&pos)) {
789217a2 828 if (e->id == Http::HdrType::FTP_PRE) {
f0174f5b
CT
829 // assume RFC 2389 FEAT response format, quoted by Squid:
830 // <"> SP NAME [SP PARAMS] <">
831 // but accommodate MS servers sending four SPs before NAME
832
833 // command name ends with (SP parameter) or quote
834 static const CharacterSet AfterFeatNameChars("AfterFeatName", " \"");
835 static const CharacterSet FeatNameChars = AfterFeatNameChars.complement("FeatName");
836
837 Parser::Tokenizer tok(SBuf(e->value.termedBuf()));
838 if (!tok.skip('"') || !tok.skip(' '))
839 continue;
840
841 // optional spaces; remember their number to accomodate MS servers
842 prependSpaces = 1 + tok.skipAll(CharacterSet::SP);
843
844 SBuf cmd;
845 if (!tok.prefix(cmd, FeatNameChars))
846 continue;
847 cmd.toUpper();
848
849 if (Ftp::SupportedCommand(cmd)) {
850 featReply->header.addEntry(e->clone());
851 }
852
853 if (cmd == cmdEprt())
854 hasEPRT = true;
855 else if (cmd == cmdEpsv())
856 hasEPSV = true;
857 }
92ae4c86 858 }
f0174f5b 859 } // else we got a FEAT error and will only report Squid-supported features
92ae4c86
AR
860
861 char buf[256];
92ae4c86
AR
862 if (!hasEPRT) {
863 snprintf(buf, sizeof(buf), "\"%*s\"", prependSpaces + 4, "EPRT");
789217a2 864 featReply->header.putStr(Http::HdrType::FTP_PRE, buf);
92ae4c86
AR
865 }
866 if (!hasEPSV) {
867 snprintf(buf, sizeof(buf), "\"%*s\"", prependSpaces + 4, "EPSV");
789217a2 868 featReply->header.putStr(Http::HdrType::FTP_PRE, buf);
92ae4c86
AR
869 }
870
f0174f5b 871 featReply->header.refreshMask();
92ae4c86 872
f0174f5b 873 writeForwardedReply(featReply.getRaw());
92ae4c86
AR
874}
875
876void
aea65fec 877Ftp::Server::handlePasvReply(const HttpReply *reply, StoreIOBuffer)
92ae4c86 878{
d3dddfb5 879 const Http::StreamPointer context(pipeline.front());
efd5e784 880 assert(context != nullptr);
92ae4c86
AR
881
882 if (context->http->request->errType != ERR_NONE) {
883 writeCustomReply(502, "Server does not support PASV", reply);
884 return;
885 }
886
887 const unsigned short localPort = listenForDataConnection();
888 if (!localPort)
889 return;
890
891 char addr[MAX_IPSTRLEN];
892 // remote server in interception setups and local address otherwise
893 const Ip::Address &server = transparent() ?
894 clientConnection->local : dataListenConn->local;
895 server.toStr(addr, MAX_IPSTRLEN, AF_INET);
896 addr[MAX_IPSTRLEN - 1] = '\0';
897 for (char *c = addr; *c != '\0'; ++c) {
898 if (*c == '.')
899 *c = ',';
900 }
901
902 // In interception setups, we combine remote server address with a
903 // local port number and hope that traffic will be redirected to us.
904 // Do not use "227 =a,b,c,d,p1,p2" format or omit parens: some nf_ct_ftp
905 // versions block responses that use those alternative syntax rules!
906 MemBuf mb;
907 mb.init();
4391cd15 908 mb.appendf("227 Entering Passive Mode (%s,%i,%i).\r\n",
f680026f
SM
909 addr,
910 static_cast<int>(localPort / 256),
911 static_cast<int>(localPort % 256));
aea65fec 912 debugs(9, 3, Raw("writing", mb.buf, mb.size));
92ae4c86
AR
913 writeReply(mb);
914}
915
916void
aea65fec 917Ftp::Server::handlePortReply(const HttpReply *reply, StoreIOBuffer)
92ae4c86 918{
efd5e784 919 if (pipeline.front()->http->request->errType != ERR_NONE) {
92ae4c86
AR
920 writeCustomReply(502, "Server does not support PASV (converted from PORT)", reply);
921 return;
922 }
923
924 writeCustomReply(200, "PORT successfully converted to PASV.");
925
926 // and wait for RETR
927}
928
929void
aea65fec 930Ftp::Server::handleErrorReply(const HttpReply *reply, StoreIOBuffer)
92ae4c86
AR
931{
932 if (!pinning.pinned) // we failed to connect to server
1ab04517 933 uri.clear();
92ae4c86
AR
934 // 421: we will close due to fssError
935 writeErrorReply(reply, 421);
936}
937
938void
939Ftp::Server::handleDataReply(const HttpReply *reply, StoreIOBuffer data)
940{
941 if (reply != NULL && reply->sline.status() != Http::scOkay) {
942 writeForwardedReply(reply);
943 if (Comm::IsConnOpen(dataConn)) {
944 debugs(33, 3, "closing " << dataConn << " on KO reply");
945 closeDataConnection();
946 }
947 return;
948 }
949
950 if (!dataConn) {
951 // We got STREAM_COMPLETE (or error) and closed the client data conn.
952 debugs(33, 3, "ignoring FTP srv data response after clt data closure");
953 return;
954 }
955
956 if (!checkDataConnPost()) {
957 writeCustomReply(425, "Data connection is not established.");
958 closeDataConnection();
959 return;
960 }
961
e7ce227f 962 debugs(33, 7, data.length);
92ae4c86
AR
963
964 if (data.length <= 0) {
965 replyDataWritingCheckpoint(); // skip the actual write call
966 return;
967 }
968
969 MemBuf mb;
970 mb.init(data.length + 1, data.length + 1);
971 mb.append(data.data, data.length);
972
973 typedef CommCbMemFunT<Server, CommIoCbParams> Dialer;
974 AsyncCall::Pointer call = JobCallback(33, 5, Dialer, this, Ftp::Server::wroteReplyData);
975 Comm::Write(dataConn, &mb, call);
976
efd5e784 977 pipeline.front()->noteSentBodyBytes(data.length);
92ae4c86
AR
978}
979
980/// called when we are done writing a chunk of the response data
981void
982Ftp::Server::wroteReplyData(const CommIoCbParams &io)
983{
984 if (io.flag == Comm::ERR_CLOSING)
985 return;
986
987 if (io.flag != Comm::OK) {
aea65fec 988 debugs(33, 3, "FTP reply data writing failed: " << xstrerr(io.xerrno));
92cfc72f 989 userDataCompletionCheckpoint(426);
92ae4c86
AR
990 return;
991 }
992
efd5e784
AJ
993 assert(pipeline.front()->http);
994 pipeline.front()->http->out.size += io.size;
92ae4c86
AR
995 replyDataWritingCheckpoint();
996}
997
998/// ClientStream checks after (actual or skipped) reply data writing
999void
27c841f6
AR
1000Ftp::Server::replyDataWritingCheckpoint()
1001{
efd5e784 1002 switch (pipeline.front()->socketState()) {
92ae4c86
AR
1003 case STREAM_NONE:
1004 debugs(33, 3, "Keep going");
efd5e784 1005 pipeline.front()->pullData();
92ae4c86
AR
1006 return;
1007 case STREAM_COMPLETE:
e7ce227f 1008 debugs(33, 3, "FTP reply data transfer successfully complete");
92cfc72f 1009 userDataCompletionCheckpoint(226);
92ae4c86
AR
1010 break;
1011 case STREAM_UNPLANNED_COMPLETE:
e7ce227f 1012 debugs(33, 3, "FTP reply data transfer failed: STREAM_UNPLANNED_COMPLETE");
92cfc72f 1013 userDataCompletionCheckpoint(451);
92ae4c86
AR
1014 break;
1015 case STREAM_FAILED:
92cfc72f 1016 userDataCompletionCheckpoint(451);
e7ce227f 1017 debugs(33, 3, "FTP reply data transfer failed: STREAM_FAILED");
92ae4c86
AR
1018 break;
1019 default:
1020 fatal("unreachable code");
1021 }
92ae4c86
AR
1022}
1023
1024void
aea65fec 1025Ftp::Server::handleUploadReply(const HttpReply *reply, StoreIOBuffer)
92ae4c86
AR
1026{
1027 writeForwardedReply(reply);
1028 // note that the client data connection may already be closed by now
1029}
1030
1031void
1032Ftp::Server::writeForwardedReply(const HttpReply *reply)
1033{
0d253dfa
CT
1034 Must(reply);
1035
3238b9b6
CT
1036 if (waitingForOrigin) {
1037 Must(delayedReply == NULL);
1038 delayedReply = reply;
1039 return;
1040 }
1041
92ae4c86 1042 const HttpHeader &header = reply->header;
789217a2
FC
1043 // adaptation and forwarding errors lack Http::HdrType::FTP_STATUS
1044 if (!header.has(Http::HdrType::FTP_STATUS)) {
92ae4c86
AR
1045 writeForwardedForeign(reply); // will get to Ftp::Server::wroteReply
1046 return;
1047 }
1048
1049 typedef CommCbMemFunT<Server, CommIoCbParams> Dialer;
1050 AsyncCall::Pointer call = JobCallback(33, 5, Dialer, this, Ftp::Server::wroteReply);
1051 writeForwardedReplyAndCall(reply, call);
1052}
1053
1054void
aea65fec 1055Ftp::Server::handleEprtReply(const HttpReply *reply, StoreIOBuffer)
92ae4c86 1056{
efd5e784 1057 if (pipeline.front()->http->request->errType != ERR_NONE) {
92ae4c86
AR
1058 writeCustomReply(502, "Server does not support PASV (converted from EPRT)", reply);
1059 return;
1060 }
1061
1062 writeCustomReply(200, "EPRT successfully converted to PASV.");
1063
1064 // and wait for RETR
1065}
1066
1067void
aea65fec 1068Ftp::Server::handleEpsvReply(const HttpReply *reply, StoreIOBuffer)
92ae4c86 1069{
efd5e784 1070 if (pipeline.front()->http->request->errType != ERR_NONE) {
92ae4c86
AR
1071 writeCustomReply(502, "Cannot connect to server", reply);
1072 return;
1073 }
1074
1075 const unsigned short localPort = listenForDataConnection();
1076 if (!localPort)
1077 return;
1078
aea65fec
AR
1079 // In interception setups, we use a local port number and hope that data
1080 // traffic will be redirected to us.
92ae4c86
AR
1081 MemBuf mb;
1082 mb.init();
4391cd15 1083 mb.appendf("229 Entering Extended Passive Mode (|||%u|)\r\n", localPort);
92ae4c86 1084
aea65fec 1085 debugs(9, 3, Raw("writing", mb.buf, mb.size));
92ae4c86
AR
1086 writeReply(mb);
1087}
1088
1089/// writes FTP error response with given status and reply-derived error details
1090void
1091Ftp::Server::writeErrorReply(const HttpReply *reply, const int scode)
1092{
efd5e784 1093 const HttpRequest *request = pipeline.front()->http->request;
92ae4c86
AR
1094 assert(request);
1095
1096 MemBuf mb;
1097 mb.init();
1098
1099 if (request->errType != ERR_NONE)
4391cd15 1100 mb.appendf("%i-%s\r\n", scode, errorPageName(request->errType));
92ae4c86
AR
1101
1102 if (request->errDetail > 0) {
1103 // XXX: > 0 may not always mean that this is an errno
4391cd15 1104 mb.appendf("%i-Error: (%d) %s\r\n", scode,
f680026f
SM
1105 request->errDetail,
1106 strerror(request->errDetail));
92ae4c86
AR
1107 }
1108
02e1ed26 1109#if USE_ADAPTATION
92ae4c86
AR
1110 // XXX: Remove hard coded names. Use an error page template instead.
1111 const Adaptation::History::Pointer ah = request->adaptHistory();
1112 if (ah != NULL) { // XXX: add adapt::<all_h but use lastMeta here
1113 const String info = ah->allMeta.getByName("X-Response-Info");
1114 const String desc = ah->allMeta.getByName("X-Response-Desc");
1115 if (info.size())
4391cd15 1116 mb.appendf("%i-Information: %s\r\n", scode, info.termedBuf());
92ae4c86 1117 if (desc.size())
4391cd15 1118 mb.appendf("%i-Description: %s\r\n", scode, desc.termedBuf());
92ae4c86 1119 }
02e1ed26 1120#endif
92ae4c86 1121
9118c951
CT
1122 const char *reason = "Lost Error";
1123 if (reply) {
1124 reason = reply->header.has(Http::HdrType::FTP_REASON) ?
1125 reply->header.getStr(Http::HdrType::FTP_REASON):
1126 reply->sline.reason();
1127 }
92ae4c86 1128
4391cd15 1129 mb.appendf("%i %s\r\n", scode, reason); // error terminating line
92ae4c86
AR
1130
1131 // TODO: errorpage.cc should detect FTP client and use
1132 // configurable FTP-friendly error templates which we should
1133 // write to the client "as is" instead of hiding most of the info
1134
1135 writeReply(mb);
1136}
1137
1138/// writes FTP response based on HTTP reply that is not an FTP-response wrapper
eacfca83 1139/// for example, internally-generated Squid "errorpages" end up here (for now)
92ae4c86
AR
1140void
1141Ftp::Server::writeForwardedForeign(const HttpReply *reply)
1142{
1143 changeState(fssConnected, "foreign reply");
1144 closeDataConnection();
1145 // 451: We intend to keep the control connection open.
1146 writeErrorReply(reply, 451);
1147}
1148
2f97ab10 1149bool
84540b47 1150Ftp::Server::writeControlMsgAndCall(HttpReply *reply, AsyncCall::Pointer &call)
92ae4c86
AR
1151{
1152 // the caller guarantees that we are dealing with the current context only
789217a2 1153 // the caller should also make sure reply->header.has(Http::HdrType::FTP_STATUS)
92ae4c86 1154 writeForwardedReplyAndCall(reply, call);
2f97ab10 1155 return true;
92ae4c86
AR
1156}
1157
1158void
1159Ftp::Server::writeForwardedReplyAndCall(const HttpReply *reply, AsyncCall::Pointer &call)
1160{
1161 assert(reply != NULL);
1162 const HttpHeader &header = reply->header;
1163
1164 // without status, the caller must use the writeForwardedForeign() path
789217a2
FC
1165 Must(header.has(Http::HdrType::FTP_STATUS));
1166 Must(header.has(Http::HdrType::FTP_REASON));
1167 const int scode = header.getInt(Http::HdrType::FTP_STATUS);
e7ce227f 1168 debugs(33, 7, "scode: " << scode);
92ae4c86
AR
1169
1170 // Status 125 or 150 implies upload or data request, but we still check
1171 // the state in case the server is buggy.
1172 if ((scode == 125 || scode == 150) &&
aea65fec
AR
1173 (master->serverState == fssHandleUploadRequest ||
1174 master->serverState == fssHandleDataRequest)) {
92ae4c86
AR
1175 if (checkDataConnPost()) {
1176 // If the data connection is ready, start reading data (here)
1177 // and forward the response to client (further below).
1178 debugs(33, 7, "data connection established, start data transfer");
aea65fec 1179 if (master->serverState == fssHandleUploadRequest)
92ae4c86
AR
1180 maybeReadUploadData();
1181 } else {
1182 // If we are waiting to accept the data connection, keep waiting.
1183 if (Comm::IsConnOpen(dataListenConn)) {
1184 debugs(33, 7, "wait for the client to establish a data connection");
1185 onDataAcceptCall = call;
1186 // TODO: Add connect timeout for passive connections listener?
1187 // TODO: Remember server response so that we can forward it?
1188 } else {
1189 // Either the connection was establised and closed after the
1190 // data was transferred OR we failed to establish an active
1191 // data connection and already sent the error to the client.
1192 // In either case, there is nothing more to do.
1193 debugs(33, 7, "done with data OR active connection failed");
1194 }
1195 return;
1196 }
1197 }
1198
1199 MemBuf mb;
1200 mb.init();
1201 Ftp::PrintReply(mb, reply);
1202
aea65fec
AR
1203 debugs(9, 2, "FTP Client " << clientConnection);
1204 debugs(9, 2, "FTP Client REPLY:\n---------\n" << mb.buf <<
92ae4c86
AR
1205 "\n----------");
1206
1207 Comm::Write(clientConnection, &mb, call);
1208}
1209
1210static void
ced8def3 1211Ftp::PrintReply(MemBuf &mb, const HttpReply *reply, const char *const)
92ae4c86
AR
1212{
1213 const HttpHeader &header = reply->header;
1214
1215 HttpHeaderPos pos = HttpHeaderInitPos;
1216 while (const HttpHeaderEntry *e = header.getEntry(&pos)) {
789217a2 1217 if (e->id == Http::HdrType::FTP_PRE) {
92ae4c86
AR
1218 String raw;
1219 if (httpHeaderParseQuotedString(e->value.rawBuf(), e->value.size(), &raw))
4391cd15 1220 mb.appendf("%s\r\n", raw.termedBuf());
92ae4c86
AR
1221 }
1222 }
1223
789217a2
FC
1224 if (header.has(Http::HdrType::FTP_STATUS)) {
1225 const char *reason = header.getStr(Http::HdrType::FTP_REASON);
1226 mb.appendf("%i %s\r\n", header.getInt(Http::HdrType::FTP_STATUS),
f680026f 1227 (reason ? reason : 0));
92ae4c86
AR
1228 }
1229}
1230
1231void
1232Ftp::Server::wroteEarlyReply(const CommIoCbParams &io)
1233{
1234 if (io.flag == Comm::ERR_CLOSING)
1235 return;
1236
1237 if (io.flag != Comm::OK) {
1238 debugs(33, 3, "FTP reply writing failed: " << xstrerr(io.xerrno));
1239 io.conn->close();
1240 return;
1241 }
1242
d3dddfb5 1243 Http::StreamPointer context = pipeline.front();
efd5e784 1244 if (context != nullptr && context->http) {
92ae4c86
AR
1245 context->http->out.size += io.size;
1246 context->http->out.headers_sz += io.size;
1247 }
1248
1249 flags.readMore = true;
1250 readSomeData();
1251}
1252
1253void
1254Ftp::Server::wroteReply(const CommIoCbParams &io)
1255{
1256 if (io.flag == Comm::ERR_CLOSING)
1257 return;
1258
1259 if (io.flag != Comm::OK) {
1260 debugs(33, 3, "FTP reply writing failed: " << xstrerr(io.xerrno));
1261 io.conn->close();
1262 return;
1263 }
1264
d3dddfb5 1265 Http::StreamPointer context = pipeline.front();
92ae4c86
AR
1266 assert(context->http);
1267 context->http->out.size += io.size;
1268 context->http->out.headers_sz += io.size;
1269
aea65fec 1270 if (master->serverState == fssError) {
92ae4c86
AR
1271 debugs(33, 5, "closing on FTP server error");
1272 io.conn->close();
1273 return;
1274 }
1275
1276 const clientStream_status_t socketState = context->socketState();
1277 debugs(33, 5, "FTP client stream state " << socketState);
1278 switch (socketState) {
1279 case STREAM_UNPLANNED_COMPLETE:
1280 case STREAM_FAILED:
27c841f6
AR
1281 io.conn->close();
1282 return;
92ae4c86
AR
1283
1284 case STREAM_NONE:
1285 case STREAM_COMPLETE:
1286 flags.readMore = true;
1287 changeState(fssConnected, "Ftp::Server::wroteReply");
fcc444e3 1288 if (bodyParser)
92ae4c86 1289 finishDechunkingRequest(false);
d20f59e2 1290 context->finished();
cb7fa5cf 1291 kick();
92ae4c86
AR
1292 return;
1293 }
1294}
1295
1296bool
eacfca83 1297Ftp::Server::handleRequest(HttpRequest *request)
27c841f6 1298{
eacfca83 1299 debugs(33, 9, request);
92ae4c86
AR
1300 Must(request);
1301
eacfca83 1302 HttpHeader &header = request->header;
789217a2
FC
1303 Must(header.has(Http::HdrType::FTP_COMMAND));
1304 String &cmd = header.findEntry(Http::HdrType::FTP_COMMAND)->value;
1305 Must(header.has(Http::HdrType::FTP_ARGUMENTS));
1306 String &params = header.findEntry(Http::HdrType::FTP_ARGUMENTS)->value;
eacfca83 1307
014adac1 1308 if (Debug::Enabled(9, 2)) {
92ae4c86 1309 MemBuf mb;
92ae4c86 1310 mb.init();
10201568 1311 request->pack(&mb);
92ae4c86 1312
aea65fec
AR
1313 debugs(9, 2, "FTP Client " << clientConnection);
1314 debugs(9, 2, "FTP Client REQUEST:\n---------\n" << mb.buf <<
92ae4c86
AR
1315 "\n----------");
1316 }
1317
1ab04517
AR
1318 // TODO: When HttpHeader uses SBuf, change keys to SBuf
1319 typedef std::map<const std::string, RequestHandler> RequestHandlers;
1320 static RequestHandlers handlers;
1321 if (!handlers.size()) {
1322 handlers["LIST"] = &Ftp::Server::handleDataRequest;
1323 handlers["NLST"] = &Ftp::Server::handleDataRequest;
1324 handlers["MLSD"] = &Ftp::Server::handleDataRequest;
1325 handlers["FEAT"] = &Ftp::Server::handleFeatRequest;
1326 handlers["PASV"] = &Ftp::Server::handlePasvRequest;
1327 handlers["PORT"] = &Ftp::Server::handlePortRequest;
1328 handlers["RETR"] = &Ftp::Server::handleDataRequest;
1329 handlers["EPRT"] = &Ftp::Server::handleEprtRequest;
1330 handlers["EPSV"] = &Ftp::Server::handleEpsvRequest;
1331 handlers["CWD"] = &Ftp::Server::handleCwdRequest;
1332 handlers["PASS"] = &Ftp::Server::handlePassRequest;
1333 handlers["CDUP"] = &Ftp::Server::handleCdupRequest;
1334 }
92ae4c86
AR
1335
1336 RequestHandler handler = NULL;
1337 if (request->method == Http::METHOD_PUT)
1338 handler = &Ftp::Server::handleUploadRequest;
1339 else {
1ab04517
AR
1340 const RequestHandlers::const_iterator hi = handlers.find(cmd.termedBuf());
1341 if (hi != handlers.end())
1342 handler = hi->second;
92ae4c86
AR
1343 }
1344
1ab04517 1345 if (!handler) {
aea65fec 1346 debugs(9, 7, "forwarding " << cmd << " as is, no post-processing");
1ab04517
AR
1347 return true;
1348 }
1349
1350 return (this->*handler)(cmd, params);
92ae4c86
AR
1351}
1352
1353/// Called to parse USER command, which is required to create an HTTP request
eacfca83 1354/// wrapper. W/o request, the errors are handled by returning earlyError().
d3dddfb5 1355Http::Stream *
ced8def3 1356Ftp::Server::handleUserRequest(const SBuf &, SBuf &params)
92ae4c86 1357{
eacfca83 1358 if (params.isEmpty())
3aab028c 1359 return earlyError(EarlyErrorKind::MissingUsername);
92ae4c86 1360
e7ce227f 1361 // find the [end of] user name
1ab04517 1362 const SBuf::size_type eou = params.rfind('@');
eacfca83 1363 if (eou == SBuf::npos || eou + 1 >= params.length())
3aab028c 1364 return earlyError(EarlyErrorKind::MissingHost);
92ae4c86 1365
e7ce227f 1366 // Determine the intended destination.
1ab04517 1367 host = params.substr(eou + 1, params.length());
92ae4c86
AR
1368 // If we can parse it as raw IPv6 address, then surround with "[]".
1369 // Otherwise (domain, IPv4, [bracketed] IPv6, garbage, etc), use as is.
1ab04517
AR
1370 if (host.find(':') != SBuf::npos) {
1371 const Ip::Address ipa(host.c_str());
92ae4c86 1372 if (!ipa.isAnyAddr()) {
1ab04517 1373 char ipBuf[MAX_IPSTRLEN];
92ae4c86
AR
1374 ipa.toHostStr(ipBuf, MAX_IPSTRLEN);
1375 host = ipBuf;
1376 }
1377 }
1378
1ab04517
AR
1379 // const SBuf login = params.substr(0, eou);
1380 params.chop(0, eou); // leave just the login part for the peer
1381
1382 SBuf oldUri;
aea65fec 1383 if (master->clientReadGreeting)
92ae4c86
AR
1384 oldUri = uri;
1385
aea65fec 1386 master->workingDir.clear();
1ab04517 1387 calcUri(NULL);
92ae4c86 1388
aea65fec
AR
1389 if (!master->clientReadGreeting) {
1390 debugs(9, 3, "set URI to " << uri);
92ae4c86 1391 } else if (oldUri.caseCmp(uri) == 0) {
aea65fec 1392 debugs(9, 5, "kept URI as " << oldUri);
92ae4c86 1393 } else {
aea65fec 1394 debugs(9, 3, "reset URI from " << oldUri << " to " << uri);
92ae4c86 1395 closeDataConnection();
92ae4c86 1396 unpinConnection(true); // close control connection to peer
e7ce227f 1397 resetLogin("URI reset");
92ae4c86
AR
1398 }
1399
eacfca83 1400 return NULL; // no early errors
92ae4c86
AR
1401}
1402
1403bool
ced8def3 1404Ftp::Server::handleFeatRequest(String &, String &)
92ae4c86 1405{
e7ce227f 1406 changeState(fssHandleFeat, "handleFeatRequest");
92ae4c86
AR
1407 return true;
1408}
1409
1410bool
ced8def3 1411Ftp::Server::handlePasvRequest(String &, String &params)
92ae4c86
AR
1412{
1413 if (gotEpsvAll) {
1414 setReply(500, "Bad PASV command");
1415 return false;
1416 }
1417
1418 if (params.size() > 0) {
1419 setReply(501, "Unexpected parameter");
1420 return false;
1421 }
1422
e7ce227f 1423 changeState(fssHandlePasv, "handlePasvRequest");
92ae4c86
AR
1424 // no need to fake PASV request via setDataCommand() in true PASV case
1425 return true;
1426}
1427
1428/// [Re]initializes dataConn for active data transfers. Does not connect.
1429bool
1430Ftp::Server::createDataConnection(Ip::Address cltAddr)
1431{
1432 assert(clientConnection != NULL);
1433 assert(!clientConnection->remote.isAnyAddr());
1434
1435 if (cltAddr != clientConnection->remote) {
1436 debugs(33, 2, "rogue PORT " << cltAddr << " request? ctrl: " << clientConnection->remote);
1437 // Closing the control connection would not help with attacks because
1438 // the client is evidently able to connect to us. Besides, closing
1439 // makes retrials easier for the client and more damaging to us.
1440 setReply(501, "Prohibited parameter value");
1441 return false;
1442 }
1443
1444 closeDataConnection();
1445
1446 Comm::ConnectionPointer conn = new Comm::Connection();
aea65fec 1447 conn->flags |= COMM_DOBIND;
92ae4c86 1448
f0c30889
AG
1449 if (clientConnection->flags & COMM_INTERCEPTION) {
1450 // In the case of NAT interception conn->local value is not set
1451 // because the TCP stack will automatically pick correct source
1452 // address for the data connection. We must only ensure that IP
1453 // version matches client's address.
1454 conn->local.setAnyAddr();
1455
1456 if (cltAddr.isIPv4())
1457 conn->local.setIPv4();
1458
1459 conn->remote = cltAddr;
1460 } else {
1461 // In the case of explicit-proxy the local IP of the control connection
1462 // is the Squid IP the client is knowingly talking to.
1463 //
1464 // In the case of TPROXY the IP address of the control connection is
1465 // server IP the client is connecting to, it can be spoofed by Squid.
1466 //
1467 // In both cases some clients may refuse to accept data connections if
1468 // these control connectin local-IP's are not used.
1469 conn->setAddrs(clientConnection->local, cltAddr);
1470
1471 // Using non-local addresses in TPROXY mode requires appropriate socket option.
1472 if (clientConnection->flags & COMM_TRANSPARENT)
17b07509 1473 conn->flags |= COMM_TRANSPARENT;
f0c30889
AG
1474 }
1475
92ae4c86
AR
1476 // RFC 959 requires active FTP connections to originate from port 20
1477 // but that would preclude us from supporting concurrent transfers! (XXX?)
1478 conn->local.port(0);
1479
aea65fec 1480 debugs(9, 3, "will actively connect from " << conn->local << " to " <<
92ae4c86
AR
1481 conn->remote);
1482
1483 dataConn = conn;
1484 uploadAvailSize = 0;
1485 return true;
1486}
1487
1488bool
ced8def3 1489Ftp::Server::handlePortRequest(String &, String &params)
92ae4c86
AR
1490{
1491 // TODO: Should PORT errors trigger closeDataConnection() cleanup?
1492
1493 if (gotEpsvAll) {
1494 setReply(500, "Rejecting PORT after EPSV ALL");
1495 return false;
1496 }
1497
1498 if (!params.size()) {
1499 setReply(501, "Missing parameter");
1500 return false;
1501 }
1502
1503 Ip::Address cltAddr;
1504 if (!Ftp::ParseIpPort(params.termedBuf(), NULL, cltAddr)) {
1505 setReply(501, "Invalid parameter");
1506 return false;
1507 }
1508
1509 if (!createDataConnection(cltAddr))
1510 return false;
1511
e7ce227f 1512 changeState(fssHandlePort, "handlePortRequest");
92ae4c86
AR
1513 setDataCommand();
1514 return true; // forward our fake PASV request
1515}
1516
1517bool
ced8def3 1518Ftp::Server::handleDataRequest(String &, String &)
92ae4c86
AR
1519{
1520 if (!checkDataConnPre())
1521 return false;
1522
92cfc72f 1523 master->userDataDone = 0;
3238b9b6 1524 originDataDownloadAbortedOnError = false;
92cfc72f 1525
e7ce227f 1526 changeState(fssHandleDataRequest, "handleDataRequest");
92ae4c86
AR
1527
1528 return true;
1529}
1530
1531bool
ced8def3 1532Ftp::Server::handleUploadRequest(String &, String &)
92ae4c86
AR
1533{
1534 if (!checkDataConnPre())
1535 return false;
1536
ec69bdb2 1537 if (Config.accessList.forceRequestBodyContinuation) {
efd5e784 1538 ClientHttpRequest *http = pipeline.front()->http;
ec69bdb2
CT
1539 HttpRequest *request = http->request;
1540 ACLFilledChecklist bodyContinuationCheck(Config.accessList.forceRequestBodyContinuation, request, NULL);
5810a994
EB
1541 bodyContinuationCheck.al = http->al;
1542 bodyContinuationCheck.syncAle(request, http->log_uri);
06bf5384 1543 if (bodyContinuationCheck.fastCheck().allowed()) {
ec69bdb2
CT
1544 request->forcedBodyContinuation = true;
1545 if (checkDataConnPost()) {
1546 // Write control Msg
1547 writeEarlyReply(150, "Data connection opened");
1548 maybeReadUploadData();
1549 } else {
1550 // wait for acceptDataConnection but tell it to call wroteEarlyReply
1551 // after writing "150 Data connection opened"
1552 typedef CommCbMemFunT<Server, CommIoCbParams> Dialer;
1553 AsyncCall::Pointer call = JobCallback(33, 5, Dialer, this, Ftp::Server::wroteEarlyReply);
1554 onDataAcceptCall = call;
1555 }
1556 }
1557 }
1558
e7ce227f 1559 changeState(fssHandleUploadRequest, "handleDataRequest");
92ae4c86
AR
1560
1561 return true;
1562}
1563
1564bool
ced8def3 1565Ftp::Server::handleEprtRequest(String &, String &params)
92ae4c86 1566{
aea65fec 1567 debugs(9, 3, "Process an EPRT " << params);
92ae4c86
AR
1568
1569 if (gotEpsvAll) {
1570 setReply(500, "Rejecting EPRT after EPSV ALL");
1571 return false;
1572 }
1573
1574 if (!params.size()) {
1575 setReply(501, "Missing parameter");
1576 return false;
1577 }
1578
1579 Ip::Address cltAddr;
1580 if (!Ftp::ParseProtoIpPort(params.termedBuf(), cltAddr)) {
1581 setReply(501, "Invalid parameter");
1582 return false;
1583 }
1584
1585 if (!createDataConnection(cltAddr))
1586 return false;
1587
e7ce227f 1588 changeState(fssHandleEprt, "handleEprtRequest");
92ae4c86
AR
1589 setDataCommand();
1590 return true; // forward our fake PASV request
1591}
1592
1593bool
ced8def3 1594Ftp::Server::handleEpsvRequest(String &, String &params)
92ae4c86 1595{
aea65fec 1596 debugs(9, 3, "Process an EPSV command with params: " << params);
92ae4c86
AR
1597 if (params.size() <= 0) {
1598 // treat parameterless EPSV as "use the protocol of the ctrl conn"
1599 } else if (params.caseCmp("ALL") == 0) {
1600 setReply(200, "EPSV ALL ok");
1601 gotEpsvAll = true;
1602 return false;
1603 } else if (params.cmp("2") == 0) {
1604 if (!Ip::EnableIpv6) {
1605 setReply(522, "Network protocol not supported, use (1)");
1606 return false;
1607 }
1608 } else if (params.cmp("1") != 0) {
1609 setReply(501, "Unsupported EPSV parameter");
1610 return false;
1611 }
1612
e7ce227f 1613 changeState(fssHandleEpsv, "handleEpsvRequest");
92ae4c86
AR
1614 setDataCommand();
1615 return true; // forward our fake PASV request
1616}
1617
1618bool
ced8def3 1619Ftp::Server::handleCwdRequest(String &, String &)
92ae4c86 1620{
e7ce227f 1621 changeState(fssHandleCwd, "handleCwdRequest");
92ae4c86
AR
1622 return true;
1623}
1624
1625bool
ced8def3 1626Ftp::Server::handlePassRequest(String &, String &)
92ae4c86 1627{
e7ce227f 1628 changeState(fssHandlePass, "handlePassRequest");
92ae4c86
AR
1629 return true;
1630}
1631
1632bool
ced8def3 1633Ftp::Server::handleCdupRequest(String &, String &)
92ae4c86 1634{
e7ce227f 1635 changeState(fssHandleCdup, "handleCdupRequest");
92ae4c86
AR
1636 return true;
1637}
1638
1639// Convert user PORT, EPRT, PASV, or EPSV data command to Squid PASV command.
1640// Squid FTP client decides what data command to use with peers.
1641void
1642Ftp::Server::setDataCommand()
1643{
efd5e784 1644 ClientHttpRequest *const http = pipeline.front()->http;
92ae4c86
AR
1645 assert(http != NULL);
1646 HttpRequest *const request = http->request;
1647 assert(request != NULL);
1648 HttpHeader &header = request->header;
789217a2
FC
1649 header.delById(Http::HdrType::FTP_COMMAND);
1650 header.putStr(Http::HdrType::FTP_COMMAND, "PASV");
1651 header.delById(Http::HdrType::FTP_ARGUMENTS);
1652 header.putStr(Http::HdrType::FTP_ARGUMENTS, "");
aea65fec 1653 debugs(9, 5, "client data command converted to fake PASV");
92ae4c86
AR
1654}
1655
1656/// check that client data connection is ready for future I/O or at least
1657/// has a chance of becoming ready soon.
1658bool
1659Ftp::Server::checkDataConnPre()
1660{
1661 if (Comm::IsConnOpen(dataConn))
1662 return true;
1663
1664 if (Comm::IsConnOpen(dataListenConn)) {
1665 // We are still waiting for a client to connect to us after PASV.
1666 // Perhaps client's data conn handshake has not reached us yet.
1667 // After we talk to the server, checkDataConnPost() will recheck.
1668 debugs(33, 3, "expecting clt data conn " << dataListenConn);
1669 return true;
1670 }
1671
1672 if (!dataConn || dataConn->remote.isAnyAddr()) {
1673 debugs(33, 5, "missing " << dataConn);
1674 // TODO: use client address and default port instead.
1675 setReply(425, "Use PORT or PASV first");
1676 return false;
1677 }
1678
1679 // active transfer: open a data connection from Squid to client
1680 typedef CommCbMemFunT<Server, CommConnectCbParams> Dialer;
1681 connector = JobCallback(17, 3, Dialer, this, Ftp::Server::connectedForData);
1682 Comm::ConnOpener *cs = new Comm::ConnOpener(dataConn, connector,
27c841f6 1683 Config.Timeout.connect);
92ae4c86
AR
1684 AsyncJob::Start(cs);
1685 return false; // ConnStateData::processFtpRequest waits handleConnectDone
1686}
1687
1688/// Check that client data connection is ready for immediate I/O.
1689bool
1690Ftp::Server::checkDataConnPost() const
1691{
1692 if (!Comm::IsConnOpen(dataConn)) {
1693 debugs(33, 3, "missing client data conn: " << dataConn);
1694 return false;
1695 }
1696 return true;
1697}
1698
1699/// Done establishing a data connection to the user.
1700void
1701Ftp::Server::connectedForData(const CommConnectCbParams &params)
1702{
1703 connector = NULL;
1704
1705 if (params.flag != Comm::OK) {
1706 /* it might have been a timeout with a partially open link */
1707 if (params.conn != NULL)
1708 params.conn->close();
1709 setReply(425, "Cannot open data connection.");
d3dddfb5 1710 Http::StreamPointer context = pipeline.front();
92ae4c86
AR
1711 Must(context->http);
1712 Must(context->http->storeEntry() != NULL);
1713 } else {
1714 Must(dataConn == params.conn);
1715 Must(Comm::IsConnOpen(params.conn));
1716 fd_note(params.conn->fd, "active client ftp data");
1717 }
1718
1719 doProcessRequest();
1720}
1721
1722void
1723Ftp::Server::setReply(const int code, const char *msg)
1724{
d3dddfb5 1725 Http::StreamPointer context = pipeline.front();
92ae4c86
AR
1726 ClientHttpRequest *const http = context->http;
1727 assert(http != NULL);
1728 assert(http->storeEntry() == NULL);
1729
43446566 1730 HttpReply *const reply = Ftp::HttpReplyWrapper(code, msg, Http::scNoContent, 0);
92ae4c86 1731
92ae4c86
AR
1732 clientStreamNode *const node = context->getClientReplyContext();
1733 clientReplyContext *const repContext =
1734 dynamic_cast<clientReplyContext *>(node->data.getRaw());
1735 assert(repContext != NULL);
1736
1737 RequestFlags reqFlags;
1738 reqFlags.cachable = false; // force releaseRequest() in storeCreateEntry()
1739 reqFlags.noCache = true;
1740 repContext->createStoreEntry(http->request->method, reqFlags);
1741 http->storeEntry()->replaceHttpReply(reply);
1742}
1743
0d253dfa
CT
1744void
1745Ftp::Server::callException(const std::exception &e)
1746{
1747 debugs(33, 2, "FTP::Server job caught: " << e.what());
1748 closeDataConnection();
1749 unpinConnection(true);
1750 if (Comm::IsConnOpen(clientConnection))
1751 clientConnection->close();
1752 AsyncJob::callException(e);
1753}
1754
92cfc72f 1755void
3238b9b6 1756Ftp::Server::startWaitingForOrigin()
92cfc72f 1757{
1562cfc0 1758 if (!isOpen()) // if we are closing, nothing to do
4309d507
CT
1759 return;
1760
3238b9b6
CT
1761 debugs(33, 5, "waiting for Ftp::Client data transfer to end");
1762 waitingForOrigin = true;
1763}
1764
1765void
1766Ftp::Server::stopWaitingForOrigin(int originStatus)
1767{
1768 Must(waitingForOrigin);
1769 waitingForOrigin = false;
1770
4309d507
CT
1771 if (!isOpen()) // if we are closing, nothing to do
1772 return;
1773
3238b9b6
CT
1774 // if we have already decided how to respond, respond now
1775 if (delayedReply) {
1776 HttpReply::Pointer reply = delayedReply;
1777 delayedReply = nullptr;
1778 writeForwardedReply(reply.getRaw());
1779 return; // do not completeDataDownload() after an earlier response
92cfc72f
CT
1780 }
1781
3238b9b6
CT
1782 if (master->serverState != fssHandleDataRequest)
1783 return;
1784
1785 // completeDataDownload() could be waitingForOrigin in fssHandleDataRequest
a8d1312d 1786 // Depending on which side has finished downloading first, either trust
3238b9b6
CT
1787 // master->userDataDone status or set originDataDownloadAbortedOnError:
1788 if (master->userDataDone) {
1789 // We finished downloading before Ftp::Client. Most likely, the
1790 // adaptation shortened the origin response or we hit an error.
1791 // Our status (stored in master->userDataDone) is more informative.
1792 // Use master->userDataDone; avoid originDataDownloadAbortedOnError.
1793 completeDataDownload();
1794 } else {
1795 debugs(33, 5, "too early to write the response");
1796 // Ftp::Client naturally finished downloading before us. Set
1797 // originDataDownloadAbortedOnError to overwrite future
1798 // master->userDataDone and relay Ftp::Client error, if there was
1799 // any, to the user.
1800 originDataDownloadAbortedOnError = (originStatus >= 400);
1801 }
92cfc72f
CT
1802}
1803
1804void Ftp::Server::userDataCompletionCheckpoint(int finalStatusCode)
1805{
1806 Must(!master->userDataDone);
1807 master->userDataDone = finalStatusCode;
1808
1809 if (bodyParser)
1810 finishDechunkingRequest(false);
1811
3238b9b6
CT
1812 if (waitingForOrigin) {
1813 // The completeDataDownload() is not called here unconditionally
92cfc72f
CT
1814 // because we want to signal the FTP user that we are not fully
1815 // done processing its data stream, even though all data bytes
1816 // have been sent or received already.
61beade2 1817 debugs(33, 5, "Transferring from FTP server is not complete");
92cfc72f
CT
1818 return;
1819 }
1820
3238b9b6
CT
1821 // Adjust our reply if the server aborted with an error before we are done.
1822 if (master->userDataDone == 226 && originDataDownloadAbortedOnError) {
61beade2 1823 debugs(33, 5, "Transferring from FTP server terminated with an error, adjust status code");
3238b9b6
CT
1824 master->userDataDone = 451;
1825 }
1826 completeDataDownload();
92cfc72f
CT
1827}
1828
3238b9b6 1829void Ftp::Server::completeDataDownload()
92cfc72f
CT
1830{
1831 writeCustomReply(master->userDataDone, master->userDataDone == 226 ? "Transfer complete" : "Server error; transfer aborted");
1832 closeDataConnection();
1833}
1834
43446566 1835/// Whether Squid FTP Relay supports a named feature (e.g., a command).
92ae4c86 1836static bool
1ab04517 1837Ftp::SupportedCommand(const SBuf &name)
92ae4c86 1838{
1ab04517 1839 static std::set<SBuf> BlackList;
92ae4c86 1840 if (BlackList.empty()) {
43446566 1841 /* Add FTP commands that Squid cannot relay correctly. */
92ae4c86 1842
43446566
AR
1843 // We probably do not support AUTH TLS.* and AUTH SSL,
1844 // but let's disclaim all AUTH support to KISS, for now.
1ab04517 1845 BlackList.insert(cmdAuth());
92ae4c86
AR
1846 }
1847
1848 // we claim support for all commands that we do not know about
1ab04517 1849 return BlackList.find(name) == BlackList.end();
92ae4c86
AR
1850}
1851