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