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