2 * Copyright (C) 1996-2016 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
);
64 void forwardError(err_type error
= ERR_NONE
, int xerrno
= 0);
65 void failedErrorMessage(err_type error
, int xerrno
);
66 HttpReply
*createHttpReply(const Http::StatusCode httpStatus
, const int64_t clen
= 0);
67 void handleDataRequest();
68 void startDataDownload();
69 void startDataUpload();
70 bool startDirTracking();
71 void stopDirTracking();
72 bool weAreTrackingDir() const {return savedReply
.message
!= NULL
;}
74 typedef void (Relay::*PreliminaryCb
)();
75 void forwardPreliminaryReply(const PreliminaryCb cb
);
76 void proceedAfterPreliminaryReply();
77 PreliminaryCb thePreliminaryCb
;
79 typedef void (Relay::*SM_FUNC
)();
80 static const SM_FUNC SM_FUNCS
[];
87 void readTransferDoneReply();
89 void readCwdOrCdupReply();
90 void readUserOrPassReply();
92 void scheduleReadControlReply();
93 void finalizeDataDownload();
95 static void abort(void *d
); // TODO: Capitalize this and FwdState::abort().
97 bool forwardingCompleted
; ///< completeForwarding() has been called
100 wordlist
*message
; ///< reply message, one wordlist entry per message line
101 char *lastCommand
; ///< the command caused the reply
102 char *lastReply
; ///< last line of reply: reply status plus message
103 int replyCode
; ///< the reply status
104 } savedReply
; ///< set and delayed while we are tracking using PWD
109 CBDATA_NAMESPACED_CLASS_INIT(Ftp
, Relay
);
111 const Ftp::Relay::SM_FUNC
Ftp::Relay::SM_FUNCS
[] = {
112 &Ftp::Relay::readGreeting
, // BEGIN
113 &Ftp::Relay::readUserOrPassReply
, // SENT_USER
114 &Ftp::Relay::readUserOrPassReply
, // SENT_PASS
115 NULL
,/* &Ftp::Relay::readReply */ // SENT_TYPE
116 NULL
,/* &Ftp::Relay::readReply */ // SENT_MDTM
117 NULL
,/* &Ftp::Relay::readReply */ // SENT_SIZE
120 &Ftp::Relay::readEpsvReply
, // SENT_EPSV_ALL
121 &Ftp::Relay::readEpsvReply
, // SENT_EPSV_1
122 &Ftp::Relay::readEpsvReply
, // SENT_EPSV_2
123 &Ftp::Relay::readPasvReply
, // SENT_PASV
124 &Ftp::Relay::readCwdOrCdupReply
, // SENT_CWD
125 NULL
,/* &Ftp::Relay::readDataReply, */ // SENT_LIST
126 NULL
,/* &Ftp::Relay::readDataReply, */ // SENT_NLST
127 NULL
,/* &Ftp::Relay::readReply */ // SENT_REST
128 NULL
,/* &Ftp::Relay::readDataReply */ // SENT_RETR
129 NULL
,/* &Ftp::Relay::readReply */ // SENT_STOR
130 NULL
,/* &Ftp::Relay::readReply */ // SENT_QUIT
131 &Ftp::Relay::readTransferDoneReply
, // READING_DATA
132 &Ftp::Relay::readReply
, // WRITING_DATA
133 NULL
,/* &Ftp::Relay::readReply */ // SENT_MKDIR
134 &Ftp::Relay::readFeatReply
, // SENT_FEAT
135 NULL
,/* &Ftp::Relay::readPwdReply */ // SENT_PWD
136 &Ftp::Relay::readCwdOrCdupReply
, // SENT_CDUP
137 &Ftp::Relay::readDataReply
,// SENT_DATA_REQUEST
138 &Ftp::Relay::readReply
, // SENT_COMMAND
142 Ftp::Relay::Relay(FwdState
*const fwdState
):
143 AsyncJob("Ftp::Relay"),
144 Ftp::Client(fwdState
),
145 thePreliminaryCb(NULL
),
146 forwardingCompleted(false)
148 savedReply
.message
= NULL
;
149 savedReply
.lastCommand
= NULL
;
150 savedReply
.lastReply
= NULL
;
151 savedReply
.replyCode
= 0;
153 // Nothing we can do at request creation time can mark the response as
154 // uncachable, unfortunately. This prevents "found KEY_PRIVATE" WARNINGs.
155 entry
->releaseRequest();
156 // TODO: Convert registerAbort() to use AsyncCall
157 entry
->registerAbort(Ftp::Relay::abort
, this);
162 closeServer(); // TODO: move to clients/Client.cc?
163 if (savedReply
.message
)
164 wordlistDestroy(&savedReply
.message
);
166 xfree(savedReply
.lastCommand
);
167 xfree(savedReply
.lastReply
);
173 if (!master().clientReadGreeting
)
174 Ftp::Client::start();
175 else if (serverState() == fssHandleDataRequest
||
176 serverState() == fssHandleUploadRequest
)
182 /// Keep control connection for future requests, after we are done with it.
183 /// Similar to COMPLETE_PERSISTENT_MSG handling in http.cc.
185 Ftp::Relay::serverComplete()
187 CbcPointer
<ConnStateData
> &mgr
= fwd
->request
->clientConnectionManager
;
189 if (Comm::IsConnOpen(ctrl
.conn
)) {
190 debugs(9, 7, "completing FTP server " << ctrl
.conn
<<
191 " after " << ctrl
.replycode
);
192 fwd
->unregister(ctrl
.conn
);
193 if (ctrl
.replycode
== 221) { // Server sends FTP 221 before closing
194 mgr
->unpinConnection(false);
197 mgr
->pinConnection(ctrl
.conn
, fwd
->request
,
198 ctrl
.conn
->getPeer(),
199 fwd
->request
->flags
.connectionAuth
);
204 Ftp::Client::serverComplete();
207 /// Safely returns the master state,
208 /// with safety checks in case the Ftp::Server side of the master xact is gone.
210 Ftp::Relay::updateMaster()
212 CbcPointer
<ConnStateData
> &mgr
= fwd
->request
->clientConnectionManager
;
214 if (Ftp::Server
*srv
= dynamic_cast<Ftp::Server
*>(mgr
.get()))
217 // this code will not be necessary once the master is inside MasterXaction
218 debugs(9, 3, "our server side is gone: " << mgr
);
219 static Ftp::MasterState Master
;
220 Master
= Ftp::MasterState();
224 /// A const variant of updateMaster().
225 const Ftp::MasterState
&
226 Ftp::Relay::master() const
228 return const_cast<Ftp::Relay
*>(this)->updateMaster(); // avoid code dupe
231 /// Changes server state and debugs about that important event.
233 Ftp::Relay::serverState(const Ftp::ServerState newState
)
235 Ftp::ServerState
&cltState
= updateMaster().serverState
;
236 debugs(9, 3, "client state was " << cltState
<< " now: " << newState
);
241 * Ensure we do not double-complete on the forward entry.
242 * We complete forwarding when the response adaptation is over
243 * (but we may still be waiting for 226 from the FTP server) and
244 * also when we get that 226 from the server (and adaptation is done).
246 \todo Rewrite FwdState to ignore double completion?
249 Ftp::Relay::completeForwarding()
251 debugs(9, 5, forwardingCompleted
);
252 if (forwardingCompleted
)
254 forwardingCompleted
= true;
255 Ftp::Client::completeForwarding();
259 Ftp::Relay::failed(err_type error
, int xerrno
, ErrorState
*ftpErr
)
261 if (!doneWithServer())
262 serverState(fssError
);
264 // TODO: we need to customize ErrorState instead
265 if (entry
->isEmpty())
266 failedErrorMessage(error
, xerrno
); // as a reply
268 Ftp::Client::failed(error
, xerrno
, ftpErr
);
272 Ftp::Relay::failedErrorMessage(err_type error
, int xerrno
)
274 const Http::StatusCode httpStatus
= failedHttpStatus(error
);
275 HttpReply
*const reply
= createHttpReply(httpStatus
);
276 entry
->replaceHttpReply(reply
);
277 EBIT_CLR(entry
->flags
, ENTRY_FWD_HDR_WAIT
);
278 fwd
->request
->detailError(error
, xerrno
);
282 Ftp::Relay::processReplyBody()
284 debugs(9, 3, status());
286 if (EBIT_TEST(entry
->flags
, ENTRY_ABORTED
)) {
288 * probably was aborted because content length exceeds one
289 * of the maximum size limits.
291 abortOnData("entry aborted after calling appendSuccessHeader()");
295 if (master().userDataDone
) {
296 // Squid-to-client data transfer done. Abort data transfer on our
297 // side to allow new commands from ftp client
298 abortOnData("Squid-to-client data connection is closed");
304 if (adaptationAccessCheckPending
) {
305 debugs(9, 3, "returning due to adaptationAccessCheckPending");
311 if (data
.readBuf
!= NULL
&& data
.readBuf
->hasContent()) {
312 const mb_size_t csize
= data
.readBuf
->contentSize();
313 debugs(9, 5, "writing " << csize
<< " bytes to the reply");
314 addVirginReplyBody(data
.readBuf
->content(), csize
);
315 data
.readBuf
->consume(csize
);
320 maybeReadVirginBody();
324 Ftp::Relay::handleControlReply()
326 if (!request
->clientConnectionManager
.valid()) {
327 debugs(9, 5, "client connection gone");
332 Ftp::Client::handleControlReply();
333 if (ctrl
.message
== NULL
)
334 return; // didn't get complete reply yet
337 assert(this->SM_FUNCS
[state
] != NULL
);
338 (this->*SM_FUNCS
[state
])();
342 Ftp::Relay::handleRequestBodyProducerAborted()
344 ::Client::handleRequestBodyProducerAborted();
346 failed(ERR_READ_ERROR
);
350 Ftp::Relay::mayReadVirginReplyBody() const
352 // TODO: move this method to the regular FTP server?
353 return Comm::IsConnOpen(data
.conn
);
357 Ftp::Relay::forwardReply()
359 assert(entry
->isEmpty());
360 EBIT_CLR(entry
->flags
, ENTRY_FWD_HDR_WAIT
);
362 HttpReply
*const reply
= createHttpReply(Http::scNoContent
);
363 reply
->sources
|= HttpMsg::srcFtp
;
365 setVirginReply(reply
);
366 adaptOrFinalizeReply();
372 Ftp::Relay::forwardPreliminaryReply(const PreliminaryCb cb
)
374 debugs(9, 5, "forwarding preliminary reply to client");
376 // we must prevent concurrent ConnStateData::sendControlMsg() calls
377 Must(thePreliminaryCb
== NULL
);
378 thePreliminaryCb
= cb
;
380 const HttpReply::Pointer reply
= createHttpReply(Http::scContinue
);
382 // the Sink will use this to call us back after writing 1xx to the client
383 typedef NullaryMemFunT
<Relay
> CbDialer
;
384 const AsyncCall::Pointer call
= JobCallback(11, 3, CbDialer
, this,
385 Ftp::Relay::proceedAfterPreliminaryReply
);
387 CallJobHere1(9, 4, request
->clientConnectionManager
, ConnStateData
,
388 ConnStateData::sendControlMsg
, HttpControlMsg(reply
, call
));
392 Ftp::Relay::proceedAfterPreliminaryReply()
394 debugs(9, 5, "proceeding after preliminary reply to client");
396 Must(thePreliminaryCb
!= NULL
);
397 const PreliminaryCb cb
= thePreliminaryCb
;
398 thePreliminaryCb
= NULL
;
403 Ftp::Relay::forwardError(err_type error
, int xerrno
)
405 failed(error
, xerrno
);
409 Ftp::Relay::createHttpReply(const Http::StatusCode httpStatus
, const int64_t clen
)
411 HttpReply
*const reply
= Ftp::HttpReplyWrapper(ctrl
.replycode
, ctrl
.last_reply
, httpStatus
, clen
);
413 for (wordlist
*W
= ctrl
.message
; W
&& W
->next
; W
= W
->next
)
414 reply
->header
.putStr(Http::HdrType::FTP_PRE
, httpHeaderQuoteString(W
->key
).c_str());
415 // no hdrCacheInit() is needed for after Http::HdrType::FTP_PRE addition
421 Ftp::Relay::handleDataRequest()
423 data
.addr(master().clientDataAddr
);
424 connectDataChannel();
428 Ftp::Relay::startDataDownload()
430 assert(Comm::IsConnOpen(data
.conn
));
432 debugs(9, 3, "begin data transfer from " << data
.conn
->remote
<<
433 " (" << data
.conn
->local
<< ")");
435 HttpReply
*const reply
= createHttpReply(Http::scOkay
, -1);
436 reply
->sources
|= HttpMsg::srcFtp
;
438 EBIT_CLR(entry
->flags
, ENTRY_FWD_HDR_WAIT
);
439 setVirginReply(reply
);
440 adaptOrFinalizeReply();
442 maybeReadVirginBody();
443 state
= READING_DATA
;
447 Ftp::Relay::startDataUpload()
449 assert(Comm::IsConnOpen(data
.conn
));
451 debugs(9, 3, "begin data transfer to " << data
.conn
->remote
<<
452 " (" << data
.conn
->local
<< ")");
454 if (!startRequestBodyFlow()) { // register to receive body data
459 state
= WRITING_DATA
;
463 Ftp::Relay::readGreeting()
465 assert(!master().clientReadGreeting
);
467 switch (ctrl
.replycode
) {
469 updateMaster().clientReadGreeting
= true;
470 if (serverState() == fssBegin
)
471 serverState(fssConnected
);
473 // Do not forward server greeting to the user because our FTP Server
474 // has greeted the user already. Also, an original origin greeting may
475 // confuse a user that has changed the origin mid-air.
480 if (NULL
!= ctrl
.message
)
481 debugs(9, DBG_IMPORTANT
, "FTP server is busy: " << ctrl
.message
->key
);
482 forwardPreliminaryReply(&Ftp::Relay::scheduleReadControlReply
);
491 Ftp::Relay::sendCommand()
493 if (!fwd
->request
->header
.has(Http::HdrType::FTP_COMMAND
)) {
494 abortAll("Internal error: FTP relay request with no command");
498 HttpHeader
&header
= fwd
->request
->header
;
499 assert(header
.has(Http::HdrType::FTP_COMMAND
));
500 const String
&cmd
= header
.findEntry(Http::HdrType::FTP_COMMAND
)->value
;
501 assert(header
.has(Http::HdrType::FTP_ARGUMENTS
));
502 const String
¶ms
= header
.findEntry(Http::HdrType::FTP_ARGUMENTS
)->value
;
504 if (params
.size() > 0)
505 debugs(9, 5, "command: " << cmd
<< ", parameters: " << params
);
507 debugs(9, 5, "command: " << cmd
<< ", no parameters");
509 if (serverState() == fssHandlePasv
||
510 serverState() == fssHandleEpsv
||
511 serverState() == fssHandleEprt
||
512 serverState() == fssHandlePort
) {
518 if (params
.size() > 0)
519 buf
.Printf("%s %s%s", cmd
.termedBuf(), params
.termedBuf(), Ftp::crlf
);
521 buf
.Printf("%s%s", cmd
.termedBuf(), Ftp::crlf
);
523 writeCommand(buf
.c_str());
526 serverState() == fssHandleCdup
? SENT_CDUP
:
527 serverState() == fssHandleCwd
? SENT_CWD
:
528 serverState() == fssHandleFeat
? SENT_FEAT
:
529 serverState() == fssHandleDataRequest
? SENT_DATA_REQUEST
:
530 serverState() == fssHandleUploadRequest
? SENT_DATA_REQUEST
:
531 serverState() == fssConnected
? SENT_USER
:
532 serverState() == fssHandlePass
? SENT_PASS
:
537 Ftp::Relay::readReply()
539 assert(serverState() == fssConnected
||
540 serverState() == fssHandleUploadRequest
);
542 if (100 <= ctrl
.replycode
&& ctrl
.replycode
< 200)
543 forwardPreliminaryReply(&Ftp::Relay::scheduleReadControlReply
);
549 Ftp::Relay::readFeatReply()
551 assert(serverState() == fssHandleFeat
);
553 if (100 <= ctrl
.replycode
&& ctrl
.replycode
< 200)
554 return; // ignore preliminary replies
560 Ftp::Relay::readPasvReply()
562 assert(serverState() == fssHandlePasv
|| serverState() == fssHandleEpsv
|| serverState() == fssHandlePort
|| serverState() == fssHandleEprt
);
564 if (100 <= ctrl
.replycode
&& ctrl
.replycode
< 200)
565 return; // ignore preliminary replies
567 if (handlePasvReply(updateMaster().clientDataAddr
))
574 Ftp::Relay::readEpsvReply()
576 if (100 <= ctrl
.replycode
&& ctrl
.replycode
< 200)
577 return; // ignore preliminary replies
579 if (handleEpsvReply(updateMaster().clientDataAddr
)) {
580 if (ctrl
.message
== NULL
)
581 return; // didn't get complete reply yet
589 Ftp::Relay::readDataReply()
591 assert(serverState() == fssHandleDataRequest
||
592 serverState() == fssHandleUploadRequest
);
594 if (ctrl
.replycode
== 125 || ctrl
.replycode
== 150) {
595 if (serverState() == fssHandleDataRequest
)
596 forwardPreliminaryReply(&Ftp::Relay::startDataDownload
);
597 else if (fwd
->request
->forcedBodyContinuation
/*&& serverState() == fssHandleUploadRequest*/)
599 else // serverState() == fssHandleUploadRequest
600 forwardPreliminaryReply(&Ftp::Relay::startDataUpload
);
606 Ftp::Relay::startDirTracking()
608 if (!fwd
->request
->clientConnectionManager
->port
->ftp_track_dirs
)
611 debugs(9, 5, "start directory tracking");
612 savedReply
.message
= ctrl
.message
;
613 savedReply
.lastCommand
= ctrl
.last_command
;
614 savedReply
.lastReply
= ctrl
.last_reply
;
615 savedReply
.replyCode
= ctrl
.replycode
;
617 ctrl
.last_command
= NULL
;
618 ctrl
.last_reply
= NULL
;
621 writeCommand("PWD\r\n");
626 Ftp::Relay::stopDirTracking()
628 debugs(9, 5, "got code from pwd: " << ctrl
.replycode
<< ", msg: " << ctrl
.last_reply
);
630 if (ctrl
.replycode
== 257)
631 updateMaster().workingDir
= Ftp::UnescapeDoubleQuoted(ctrl
.last_reply
);
633 wordlistDestroy(&ctrl
.message
);
634 safe_free(ctrl
.last_command
);
635 safe_free(ctrl
.last_reply
);
637 ctrl
.message
= savedReply
.message
;
638 ctrl
.last_command
= savedReply
.lastCommand
;
639 ctrl
.last_reply
= savedReply
.lastReply
;
640 ctrl
.replycode
= savedReply
.replyCode
;
642 savedReply
.message
= NULL
;
643 savedReply
.lastReply
= NULL
;
644 savedReply
.lastCommand
= NULL
;
648 Ftp::Relay::readCwdOrCdupReply()
650 assert(serverState() == fssHandleCwd
||
651 serverState() == fssHandleCdup
);
653 debugs(9, 5, "got code " << ctrl
.replycode
<< ", msg: " << ctrl
.last_reply
);
655 if (100 <= ctrl
.replycode
&& ctrl
.replycode
< 200)
658 if (weAreTrackingDir()) { // we are tracking
659 stopDirTracking(); // and forward the delayed response below
660 } else if (startDirTracking())
667 Ftp::Relay::readUserOrPassReply()
669 if (100 <= ctrl
.replycode
&& ctrl
.replycode
< 200)
670 return; //Just ignore
672 if (weAreTrackingDir()) { // we are tracking
673 stopDirTracking(); // and forward the delayed response below
674 } else if (ctrl
.replycode
== 230) { // successful login
675 if (startDirTracking())
683 Ftp::Relay::readTransferDoneReply()
685 debugs(9, 3, status());
687 if (ctrl
.replycode
!= 226 && ctrl
.replycode
!= 250) {
688 debugs(9, DBG_IMPORTANT
, "got FTP code " << ctrl
.replycode
<<
689 " after reading response data");
692 finalizeDataDownload();
696 Ftp::Relay::dataChannelConnected(const CommConnectCbParams
&io
)
698 debugs(9, 3, status());
701 if (io
.flag
!= Comm::OK
) {
702 debugs(9, 2, "failed to connect FTP server data channel");
703 forwardError(ERR_CONNECT_FAIL
, io
.xerrno
);
707 debugs(9, 2, "connected FTP server data channel: " << io
.conn
);
709 data
.opened(io
.conn
, dataCloser());
715 Ftp::Relay::scheduleReadControlReply()
717 Ftp::Client::scheduleReadControlReply(0);
721 Ftp::Relay::finalizeDataDownload()
723 debugs(9, 2, "Complete data downloading/Uploading");
725 updateMaster().waitForOriginData
= false;
727 CbcPointer
<ConnStateData
> &mgr
= fwd
->request
->clientConnectionManager
;
729 if (Ftp::Server
*srv
= dynamic_cast<Ftp::Server
*>(mgr
.get())) {
730 typedef NullaryMemFunT
<Ftp::Server
> CbDialer
;
731 AsyncCall::Pointer call
= JobCallback(11, 3, CbDialer
, srv
,
732 Ftp::Server::originDataCompletionCheckpoint
);
733 ScheduleCallHere(call
);
740 Ftp::Relay::abortOnData(const char *reason
)
742 debugs(9, 3, "aborting transaction for " << reason
<<
743 "; FD " << (ctrl
.conn
!= NULL
? ctrl
.conn
->fd
: -1) << ", Data FD " << (data
.conn
!= NULL
? data
.conn
->fd
: -1) << ", this " << this);
744 // this method is only called to handle data connection problems
745 // the control connection should keep going
748 if (adaptedBodySource
!= NULL
)
749 stopConsumingFrom(adaptedBodySource
);
752 if (Comm::IsConnOpen(data
.conn
))
755 return !Comm::IsConnOpen(ctrl
.conn
);
759 Ftp::Relay::abort(void *d
)
761 Ftp::Relay
*ftpClient
= (Ftp::Relay
*)d
;
762 debugs(9, 2, "Client Data connection closed!");
763 if (!cbdataReferenceValid(ftpClient
))
765 if (Comm::IsConnOpen(ftpClient
->data
.conn
))
766 ftpClient
->dataComplete();
770 Ftp::StartRelay(FwdState
*const fwdState
)
772 return AsyncJob::Start(new Ftp::Relay(fwdState
));