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