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