2 * Copyright (C) 1996-2020 The Squid Software Foundation and contributors
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.
9 /* DEBUG: section 09 File Transfer Protocol (FTP) */
12 #include "anyp/PortCfg.h"
13 #include "client_side.h"
14 #include "clients/forward.h"
15 #include "clients/FtpClient.h"
16 #include "ftp/Elements.h"
17 #include "ftp/Parsing.h"
18 #include "http/Stream.h"
19 #include "HttpHdrCc.h"
20 #include "HttpRequest.h"
21 #include "sbuf/SBuf.h"
22 #include "servers/FtpServer.h"
23 #include "SquidTime.h"
30 /// An FTP client receiving native FTP commands from our FTP server
31 /// (Ftp::Server), forwarding them to the next FTP hop,
32 /// and then relaying FTP replies back to our FTP server.
33 class Relay
: public Ftp::Client
38 explicit Relay(FwdState
*const fwdState
);
42 const Ftp::MasterState
&master() const;
43 Ftp::MasterState
&updateMaster();
44 Ftp::ServerState
serverState() const { return master().serverState
; }
45 void serverState(const Ftp::ServerState newState
);
48 virtual void failed(err_type error
= ERR_NONE
, int xerrno
= 0, ErrorState
*ftperr
= nullptr);
49 virtual void dataChannelConnected(const CommConnectCbParams
&io
);
52 virtual void serverComplete();
53 virtual void handleControlReply();
54 virtual void processReplyBody();
55 virtual void handleRequestBodyProducerAborted();
56 virtual bool mayReadVirginReplyBody() const;
57 virtual void completeForwarding();
58 virtual bool abortOnData(const char *reason
);
62 virtual void swanSong();
65 void forwardError(err_type error
= ERR_NONE
, int xerrno
= 0);
66 void failedErrorMessage(err_type error
, int xerrno
);
67 HttpReply
*createHttpReply(const Http::StatusCode httpStatus
, const int64_t clen
= 0);
68 void handleDataRequest();
69 void startDataDownload();
70 void startDataUpload();
71 bool startDirTracking();
72 void stopDirTracking();
73 bool weAreTrackingDir() const {return savedReply
.message
!= NULL
;}
75 typedef void (Relay::*PreliminaryCb
)();
76 void forwardPreliminaryReply(const PreliminaryCb cb
);
77 void proceedAfterPreliminaryReply();
78 PreliminaryCb thePreliminaryCb
;
80 typedef void (Relay::*SM_FUNC
)();
81 static const SM_FUNC SM_FUNCS
[];
88 void readTransferDoneReply();
90 void readCwdOrCdupReply();
91 void readUserOrPassReply();
93 void scheduleReadControlReply();
95 /// Inform Ftp::Server that we are done if originWaitInProgress
96 void stopOriginWait(int code
);
98 static void abort(void *d
); // TODO: Capitalize this and FwdState::abort().
100 bool forwardingCompleted
; ///< completeForwarding() has been called
102 /// whether we are between Ftp::Server::startWaitingForOrigin() and
103 /// Ftp::Server::stopWaitingForOrigin() calls
104 bool originWaitInProgress
;
107 wordlist
*message
; ///< reply message, one wordlist entry per message line
108 char *lastCommand
; ///< the command caused the reply
109 char *lastReply
; ///< last line of reply: reply status plus message
110 int replyCode
; ///< the reply status
111 } savedReply
; ///< set and delayed while we are tracking using PWD
116 CBDATA_NAMESPACED_CLASS_INIT(Ftp
, Relay
);
118 const Ftp::Relay::SM_FUNC
Ftp::Relay::SM_FUNCS
[] = {
119 &Ftp::Relay::readGreeting
, // BEGIN
120 &Ftp::Relay::readUserOrPassReply
, // SENT_USER
121 &Ftp::Relay::readUserOrPassReply
, // SENT_PASS
122 NULL
,/* &Ftp::Relay::readReply */ // SENT_TYPE
123 NULL
,/* &Ftp::Relay::readReply */ // SENT_MDTM
124 NULL
,/* &Ftp::Relay::readReply */ // SENT_SIZE
127 &Ftp::Relay::readEpsvReply
, // SENT_EPSV_ALL
128 &Ftp::Relay::readEpsvReply
, // SENT_EPSV_1
129 &Ftp::Relay::readEpsvReply
, // SENT_EPSV_2
130 &Ftp::Relay::readPasvReply
, // SENT_PASV
131 &Ftp::Relay::readCwdOrCdupReply
, // SENT_CWD
132 NULL
,/* &Ftp::Relay::readDataReply, */ // SENT_LIST
133 NULL
,/* &Ftp::Relay::readDataReply, */ // SENT_NLST
134 NULL
,/* &Ftp::Relay::readReply */ // SENT_REST
135 NULL
,/* &Ftp::Relay::readDataReply */ // SENT_RETR
136 NULL
,/* &Ftp::Relay::readReply */ // SENT_STOR
137 NULL
,/* &Ftp::Relay::readReply */ // SENT_QUIT
138 &Ftp::Relay::readTransferDoneReply
, // READING_DATA
139 &Ftp::Relay::readReply
, // WRITING_DATA
140 NULL
,/* &Ftp::Relay::readReply */ // SENT_MKDIR
141 &Ftp::Relay::readFeatReply
, // SENT_FEAT
142 NULL
,/* &Ftp::Relay::readPwdReply */ // SENT_PWD
143 &Ftp::Relay::readCwdOrCdupReply
, // SENT_CDUP
144 &Ftp::Relay::readDataReply
,// SENT_DATA_REQUEST
145 &Ftp::Relay::readReply
, // SENT_COMMAND
149 Ftp::Relay::Relay(FwdState
*const fwdState
):
150 AsyncJob("Ftp::Relay"),
151 Ftp::Client(fwdState
),
152 thePreliminaryCb(NULL
),
153 forwardingCompleted(false),
154 originWaitInProgress(false)
156 savedReply
.message
= NULL
;
157 savedReply
.lastCommand
= NULL
;
158 savedReply
.lastReply
= NULL
;
159 savedReply
.replyCode
= 0;
161 // Nothing we can do at request creation time can mark the response as
162 // uncachable, unfortunately. This prevents "found KEY_PRIVATE" WARNINGs.
163 entry
->releaseRequest();
164 // TODO: Convert registerAbort() to use AsyncCall
165 entry
->registerAbort(Ftp::Relay::abort
, this);
170 closeServer(); // TODO: move to clients/Client.cc?
171 if (savedReply
.message
)
172 wordlistDestroy(&savedReply
.message
);
174 xfree(savedReply
.lastCommand
);
175 xfree(savedReply
.lastReply
);
181 if (!master().clientReadGreeting
)
182 Ftp::Client::start();
183 else if (serverState() == fssHandleDataRequest
||
184 serverState() == fssHandleUploadRequest
)
191 Ftp::Relay::swanSong()
194 Ftp::Client::swanSong();
197 /// Keep control connection for future requests, after we are done with it.
198 /// Similar to COMPLETE_PERSISTENT_MSG handling in http.cc.
200 Ftp::Relay::serverComplete()
202 stopOriginWait(ctrl
.replycode
);
204 CbcPointer
<ConnStateData
> &mgr
= fwd
->request
->clientConnectionManager
;
206 if (Comm::IsConnOpen(ctrl
.conn
)) {
207 debugs(9, 7, "completing FTP server " << ctrl
.conn
<<
208 " after " << ctrl
.replycode
);
209 fwd
->unregister(ctrl
.conn
);
210 if (ctrl
.replycode
== 221) { // Server sends FTP 221 before closing
211 mgr
->unpinConnection(false);
214 CallJobHere1(9, 4, mgr
,
216 notePinnedConnectionBecameIdle
,
217 ConnStateData::PinnedIdleContext(ctrl
.conn
, fwd
->request
));
222 Ftp::Client::serverComplete();
225 /// Safely returns the master state,
226 /// with safety checks in case the Ftp::Server side of the master xact is gone.
228 Ftp::Relay::updateMaster()
230 CbcPointer
<ConnStateData
> &mgr
= fwd
->request
->clientConnectionManager
;
232 if (Ftp::Server
*srv
= dynamic_cast<Ftp::Server
*>(mgr
.get()))
235 // this code will not be necessary once the master is inside MasterXaction
236 debugs(9, 3, "our server side is gone: " << mgr
);
237 static Ftp::MasterState Master
;
238 Master
= Ftp::MasterState();
242 /// A const variant of updateMaster().
243 const Ftp::MasterState
&
244 Ftp::Relay::master() const
246 return const_cast<Ftp::Relay
*>(this)->updateMaster(); // avoid code dupe
249 /// Changes server state and debugs about that important event.
251 Ftp::Relay::serverState(const Ftp::ServerState newState
)
253 Ftp::ServerState
&cltState
= updateMaster().serverState
;
254 debugs(9, 3, "client state was " << cltState
<< " now: " << newState
);
259 * Ensure we do not double-complete on the forward entry.
260 * We complete forwarding when the response adaptation is over
261 * (but we may still be waiting for 226 from the FTP server) and
262 * also when we get that 226 from the server (and adaptation is done).
264 \todo Rewrite FwdState to ignore double completion?
267 Ftp::Relay::completeForwarding()
269 debugs(9, 5, forwardingCompleted
);
270 if (forwardingCompleted
)
272 forwardingCompleted
= true;
273 Ftp::Client::completeForwarding();
277 Ftp::Relay::failed(err_type error
, int xerrno
, ErrorState
*ftpErr
)
279 if (!doneWithServer())
280 serverState(fssError
);
282 // TODO: we need to customize ErrorState instead
283 if (entry
->isEmpty())
284 failedErrorMessage(error
, xerrno
); // as a reply
286 Ftp::Client::failed(error
, xerrno
, ftpErr
);
290 Ftp::Relay::failedErrorMessage(err_type error
, int xerrno
)
292 const Http::StatusCode httpStatus
= failedHttpStatus(error
);
293 HttpReply
*const reply
= createHttpReply(httpStatus
);
294 entry
->replaceHttpReply(reply
);
295 fwd
->request
->detailError(error
, xerrno
);
299 Ftp::Relay::processReplyBody()
301 debugs(9, 3, status());
303 if (EBIT_TEST(entry
->flags
, ENTRY_ABORTED
)) {
305 * probably was aborted because content length exceeds one
306 * of the maximum size limits.
308 abortOnData("entry aborted after calling appendSuccessHeader()");
312 if (master().userDataDone
) {
313 // Squid-to-client data transfer done. Abort data transfer on our
314 // side to allow new commands from ftp client
315 abortOnData("Squid-to-client data connection is closed");
321 if (adaptationAccessCheckPending
) {
322 debugs(9, 3, "returning due to adaptationAccessCheckPending");
328 if (data
.readBuf
!= NULL
&& data
.readBuf
->hasContent()) {
329 const mb_size_t csize
= data
.readBuf
->contentSize();
330 debugs(9, 5, "writing " << csize
<< " bytes to the reply");
331 addVirginReplyBody(data
.readBuf
->content(), csize
);
332 data
.readBuf
->consume(csize
);
337 maybeReadVirginBody();
341 Ftp::Relay::handleControlReply()
343 if (!request
->clientConnectionManager
.valid()) {
344 debugs(9, 5, "client connection gone");
349 Ftp::Client::handleControlReply();
350 if (ctrl
.message
== NULL
)
351 return; // didn't get complete reply yet
354 assert(this->SM_FUNCS
[state
] != NULL
);
355 (this->*SM_FUNCS
[state
])();
359 Ftp::Relay::handleRequestBodyProducerAborted()
361 ::Client::handleRequestBodyProducerAborted();
363 failed(ERR_READ_ERROR
);
367 Ftp::Relay::mayReadVirginReplyBody() const
369 // TODO: move this method to the regular FTP server?
370 return Comm::IsConnOpen(data
.conn
);
374 Ftp::Relay::forwardReply()
376 assert(entry
->isEmpty());
378 HttpReply
*const reply
= createHttpReply(Http::scNoContent
);
379 reply
->sources
|= Http::Message::srcFtp
;
381 setVirginReply(reply
);
382 adaptOrFinalizeReply();
388 Ftp::Relay::forwardPreliminaryReply(const PreliminaryCb cb
)
390 debugs(9, 5, "forwarding preliminary reply to client");
392 // we must prevent concurrent ConnStateData::sendControlMsg() calls
393 Must(thePreliminaryCb
== NULL
);
394 thePreliminaryCb
= cb
;
396 const HttpReply::Pointer reply
= createHttpReply(Http::scContinue
);
398 // the Sink will use this to call us back after writing 1xx to the client
399 typedef NullaryMemFunT
<Relay
> CbDialer
;
400 const AsyncCall::Pointer call
= JobCallback(11, 3, CbDialer
, this,
401 Ftp::Relay::proceedAfterPreliminaryReply
);
403 CallJobHere1(9, 4, request
->clientConnectionManager
, ConnStateData
,
404 ConnStateData::sendControlMsg
, HttpControlMsg(reply
, call
));
408 Ftp::Relay::proceedAfterPreliminaryReply()
410 debugs(9, 5, "proceeding after preliminary reply to client");
412 Must(thePreliminaryCb
!= NULL
);
413 const PreliminaryCb cb
= thePreliminaryCb
;
414 thePreliminaryCb
= NULL
;
419 Ftp::Relay::forwardError(err_type error
, int xerrno
)
421 failed(error
, xerrno
);
425 Ftp::Relay::createHttpReply(const Http::StatusCode httpStatus
, const int64_t clen
)
427 HttpReply
*const reply
= Ftp::HttpReplyWrapper(ctrl
.replycode
, ctrl
.last_reply
, httpStatus
, clen
);
429 for (wordlist
*W
= ctrl
.message
; W
&& W
->next
; W
= W
->next
)
430 reply
->header
.putStr(Http::HdrType::FTP_PRE
, httpHeaderQuoteString(W
->key
).c_str());
431 // no hdrCacheInit() is needed for after Http::HdrType::FTP_PRE addition
437 Ftp::Relay::handleDataRequest()
439 data
.addr(master().clientDataAddr
);
440 connectDataChannel();
444 Ftp::Relay::startDataDownload()
446 assert(Comm::IsConnOpen(data
.conn
));
448 debugs(9, 3, "begin data transfer from " << data
.conn
->remote
<<
449 " (" << data
.conn
->local
<< ")");
451 HttpReply
*const reply
= createHttpReply(Http::scOkay
, -1);
452 reply
->sources
|= Http::Message::srcFtp
;
454 setVirginReply(reply
);
455 adaptOrFinalizeReply();
457 maybeReadVirginBody();
458 state
= READING_DATA
;
462 Ftp::Relay::startDataUpload()
464 assert(Comm::IsConnOpen(data
.conn
));
466 debugs(9, 3, "begin data transfer to " << data
.conn
->remote
<<
467 " (" << data
.conn
->local
<< ")");
469 if (!startRequestBodyFlow()) { // register to receive body data
474 state
= WRITING_DATA
;
478 Ftp::Relay::readGreeting()
480 assert(!master().clientReadGreeting
);
482 switch (ctrl
.replycode
) {
484 updateMaster().clientReadGreeting
= true;
485 if (serverState() == fssBegin
)
486 serverState(fssConnected
);
488 // Do not forward server greeting to the user because our FTP Server
489 // has greeted the user already. Also, an original origin greeting may
490 // confuse a user that has changed the origin mid-air.
495 if (NULL
!= ctrl
.message
)
496 debugs(9, DBG_IMPORTANT
, "FTP server is busy: " << ctrl
.message
->key
);
497 forwardPreliminaryReply(&Ftp::Relay::scheduleReadControlReply
);
506 Ftp::Relay::sendCommand()
508 if (!fwd
->request
->header
.has(Http::HdrType::FTP_COMMAND
)) {
509 abortAll("Internal error: FTP relay request with no command");
513 HttpHeader
&header
= fwd
->request
->header
;
514 assert(header
.has(Http::HdrType::FTP_COMMAND
));
515 const String
&cmd
= header
.findEntry(Http::HdrType::FTP_COMMAND
)->value
;
516 assert(header
.has(Http::HdrType::FTP_ARGUMENTS
));
517 const String
¶ms
= header
.findEntry(Http::HdrType::FTP_ARGUMENTS
)->value
;
519 if (params
.size() > 0)
520 debugs(9, 5, "command: " << cmd
<< ", parameters: " << params
);
522 debugs(9, 5, "command: " << cmd
<< ", no parameters");
524 if (serverState() == fssHandlePasv
||
525 serverState() == fssHandleEpsv
||
526 serverState() == fssHandleEprt
||
527 serverState() == fssHandlePort
) {
533 if (params
.size() > 0)
534 buf
.Printf("%s %s%s", cmd
.termedBuf(), params
.termedBuf(), Ftp::crlf
);
536 buf
.Printf("%s%s", cmd
.termedBuf(), Ftp::crlf
);
538 writeCommand(buf
.c_str());
541 serverState() == fssHandleCdup
? SENT_CDUP
:
542 serverState() == fssHandleCwd
? SENT_CWD
:
543 serverState() == fssHandleFeat
? SENT_FEAT
:
544 serverState() == fssHandleDataRequest
? SENT_DATA_REQUEST
:
545 serverState() == fssHandleUploadRequest
? SENT_DATA_REQUEST
:
546 serverState() == fssConnected
? SENT_USER
:
547 serverState() == fssHandlePass
? SENT_PASS
:
550 if (state
== SENT_DATA_REQUEST
) {
551 CbcPointer
<ConnStateData
> &mgr
= fwd
->request
->clientConnectionManager
;
553 if (Ftp::Server
*srv
= dynamic_cast<Ftp::Server
*>(mgr
.get())) {
554 typedef NullaryMemFunT
<Ftp::Server
> CbDialer
;
555 AsyncCall::Pointer call
= JobCallback(11, 3, CbDialer
, srv
,
556 Ftp::Server::startWaitingForOrigin
);
557 ScheduleCallHere(call
);
558 originWaitInProgress
= true;
565 Ftp::Relay::readReply()
567 assert(serverState() == fssConnected
||
568 serverState() == fssHandleUploadRequest
);
570 if (Is1xx(ctrl
.replycode
))
571 forwardPreliminaryReply(&Ftp::Relay::scheduleReadControlReply
);
577 Ftp::Relay::readFeatReply()
579 assert(serverState() == fssHandleFeat
);
581 if (Is1xx(ctrl
.replycode
))
582 return; // ignore preliminary replies
588 Ftp::Relay::readPasvReply()
590 assert(serverState() == fssHandlePasv
|| serverState() == fssHandleEpsv
|| serverState() == fssHandlePort
|| serverState() == fssHandleEprt
);
592 if (Is1xx(ctrl
.replycode
))
593 return; // ignore preliminary replies
595 if (handlePasvReply(updateMaster().clientDataAddr
))
602 Ftp::Relay::readEpsvReply()
604 if (Is1xx(ctrl
.replycode
))
605 return; // ignore preliminary replies
607 if (handleEpsvReply(updateMaster().clientDataAddr
)) {
608 if (ctrl
.message
== NULL
)
609 return; // didn't get complete reply yet
617 Ftp::Relay::readDataReply()
619 assert(serverState() == fssHandleDataRequest
||
620 serverState() == fssHandleUploadRequest
);
622 if (ctrl
.replycode
== 125 || ctrl
.replycode
== 150) {
623 if (serverState() == fssHandleDataRequest
)
624 forwardPreliminaryReply(&Ftp::Relay::startDataDownload
);
625 else if (fwd
->request
->forcedBodyContinuation
/*&& serverState() == fssHandleUploadRequest*/)
627 else // serverState() == fssHandleUploadRequest
628 forwardPreliminaryReply(&Ftp::Relay::startDataUpload
);
634 Ftp::Relay::startDirTracking()
636 if (!fwd
->request
->clientConnectionManager
->port
->ftp_track_dirs
)
639 debugs(9, 5, "start directory tracking");
640 savedReply
.message
= ctrl
.message
;
641 savedReply
.lastCommand
= ctrl
.last_command
;
642 savedReply
.lastReply
= ctrl
.last_reply
;
643 savedReply
.replyCode
= ctrl
.replycode
;
645 ctrl
.last_command
= NULL
;
646 ctrl
.last_reply
= NULL
;
649 writeCommand("PWD\r\n");
654 Ftp::Relay::stopDirTracking()
656 debugs(9, 5, "got code from pwd: " << ctrl
.replycode
<< ", msg: " << ctrl
.last_reply
);
658 if (ctrl
.replycode
== 257)
659 updateMaster().workingDir
= Ftp::UnescapeDoubleQuoted(ctrl
.last_reply
);
661 wordlistDestroy(&ctrl
.message
);
662 safe_free(ctrl
.last_command
);
663 safe_free(ctrl
.last_reply
);
665 ctrl
.message
= savedReply
.message
;
666 ctrl
.last_command
= savedReply
.lastCommand
;
667 ctrl
.last_reply
= savedReply
.lastReply
;
668 ctrl
.replycode
= savedReply
.replyCode
;
670 savedReply
.message
= NULL
;
671 savedReply
.lastReply
= NULL
;
672 savedReply
.lastCommand
= NULL
;
676 Ftp::Relay::readCwdOrCdupReply()
678 assert(serverState() == fssHandleCwd
||
679 serverState() == fssHandleCdup
);
681 debugs(9, 5, "got code " << ctrl
.replycode
<< ", msg: " << ctrl
.last_reply
);
683 if (Is1xx(ctrl
.replycode
))
686 if (weAreTrackingDir()) { // we are tracking
687 stopDirTracking(); // and forward the delayed response below
688 } else if (startDirTracking())
695 Ftp::Relay::readUserOrPassReply()
697 if (Is1xx(ctrl
.replycode
))
698 return; //Just ignore
700 if (weAreTrackingDir()) { // we are tracking
701 stopDirTracking(); // and forward the delayed response below
702 } else if (ctrl
.replycode
== 230) { // successful login
703 if (startDirTracking())
711 Ftp::Relay::readTransferDoneReply()
713 debugs(9, 3, status());
715 if (ctrl
.replycode
!= 226 && ctrl
.replycode
!= 250) {
716 debugs(9, DBG_IMPORTANT
, "got FTP code " << ctrl
.replycode
<<
717 " after reading response data");
720 debugs(9, 2, "Complete data downloading");
726 Ftp::Relay::dataChannelConnected(const CommConnectCbParams
&io
)
728 debugs(9, 3, status());
731 if (io
.flag
!= Comm::OK
) {
732 debugs(9, 2, "failed to connect FTP server data channel");
733 forwardError(ERR_CONNECT_FAIL
, io
.xerrno
);
737 debugs(9, 2, "connected FTP server data channel: " << io
.conn
);
739 data
.opened(io
.conn
, dataCloser());
745 Ftp::Relay::scheduleReadControlReply()
747 Ftp::Client::scheduleReadControlReply(0);
751 Ftp::Relay::abortOnData(const char *reason
)
753 debugs(9, 3, "aborting transaction for " << reason
<<
754 "; FD " << (ctrl
.conn
!= NULL
? ctrl
.conn
->fd
: -1) << ", Data FD " << (data
.conn
!= NULL
? data
.conn
->fd
: -1) << ", this " << this);
755 // this method is only called to handle data connection problems
756 // the control connection should keep going
759 if (adaptedBodySource
!= NULL
)
760 stopConsumingFrom(adaptedBodySource
);
763 if (Comm::IsConnOpen(data
.conn
))
766 return !Comm::IsConnOpen(ctrl
.conn
);
770 Ftp::Relay::stopOriginWait(int code
)
772 if (originWaitInProgress
) {
773 CbcPointer
<ConnStateData
> &mgr
= fwd
->request
->clientConnectionManager
;
775 if (Ftp::Server
*srv
= dynamic_cast<Ftp::Server
*>(mgr
.get())) {
776 typedef UnaryMemFunT
<Ftp::Server
, int> CbDialer
;
777 AsyncCall::Pointer call
= asyncCall(11, 3, "Ftp::Server::stopWaitingForOrigin",
778 CbDialer(srv
, &Ftp::Server::stopWaitingForOrigin
, code
));
779 ScheduleCallHere(call
);
782 originWaitInProgress
= false;
787 Ftp::Relay::abort(void *d
)
789 Ftp::Relay
*ftpClient
= (Ftp::Relay
*)d
;
790 debugs(9, 2, "Client Data connection closed!");
791 if (!cbdataReferenceValid(ftpClient
))
793 if (Comm::IsConnOpen(ftpClient
->data
.conn
))
794 ftpClient
->dataComplete();
798 Ftp::StartRelay(FwdState
*const fwdState
)
800 return AsyncJob::Start(new Ftp::Relay(fwdState
));