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