2 * DEBUG: section 09 File Transfer Protocol (FTP)
7 #include "anyp/PortCfg.h"
8 #include "client_side.h"
9 #include "clients/forward.h"
10 #include "clients/FtpClient.h"
11 #include "ftp/Elements.h"
12 #include "ftp/Parsing.h"
13 #include "HttpHdrCc.h"
14 #include "HttpRequest.h"
17 #include "servers/FtpServer.h"
18 #include "SquidTime.h"
25 /// An FTP client receiving native FTP commands from our FTP server
26 /// (Ftp::Server), forwarding them to the next FTP hop,
27 /// and then relaying FTP replies back to our FTP server.
28 class Relay
: public Ftp::Client
31 explicit Relay(FwdState
*const fwdState
);
35 const Ftp::MasterState
&master() const;
36 Ftp::MasterState
&updateMaster();
37 Ftp::ServerState
serverState() const { return master().serverState
; }
38 void serverState(const Ftp::ServerState newState
);
41 virtual void failed(err_type error
= ERR_NONE
, int xerrno
= 0);
42 virtual void dataChannelConnected(const CommConnectCbParams
&io
);
44 /* ServerStateData API */
45 virtual void serverComplete();
46 virtual void handleControlReply();
47 virtual void processReplyBody();
48 virtual void handleRequestBodyProducerAborted();
49 virtual bool mayReadVirginReplyBody() const;
50 virtual void completeForwarding();
56 void forwardError(err_type error
= ERR_NONE
, int xerrno
= 0);
57 void failedErrorMessage(err_type error
, int xerrno
);
58 HttpReply
*createHttpReply(const Http::StatusCode httpStatus
, const int64_t clen
= 0);
59 void handleDataRequest();
60 void startDataDownload();
61 void startDataUpload();
62 bool startDirTracking();
63 void stopDirTracking();
64 bool weAreTrackingDir() const {return savedReply
.message
!= NULL
;}
66 typedef void (Relay::*PreliminaryCb
)();
67 void forwardPreliminaryReply(const PreliminaryCb cb
);
68 void proceedAfterPreliminaryReply();
69 PreliminaryCb thePreliminaryCb
;
71 typedef void (Relay::*SM_FUNC
)();
72 static const SM_FUNC SM_FUNCS
[];
79 void readTransferDoneReply();
81 void readCwdOrCdupReply();
82 void readUserOrPassReply();
84 void scheduleReadControlReply();
86 bool forwardingCompleted
; ///< completeForwarding() has been called
89 wordlist
*message
; ///< reply message, one wordlist entry per message line
90 char *lastCommand
; ///< the command caused the reply
91 char *lastReply
; ///< last line of reply: reply status plus message
92 int replyCode
; ///< the reply status
93 } savedReply
; ///< set and delayed while we are tracking using PWD
100 CBDATA_NAMESPACED_CLASS_INIT(Ftp
, Relay
);
102 const Ftp::Relay::SM_FUNC
Ftp::Relay::SM_FUNCS
[] = {
103 &Ftp::Relay::readGreeting
, // BEGIN
104 &Ftp::Relay::readUserOrPassReply
, // SENT_USER
105 &Ftp::Relay::readUserOrPassReply
, // SENT_PASS
106 NULL
,/* &Ftp::Relay::readReply */ // SENT_TYPE
107 NULL
,/* &Ftp::Relay::readReply */ // SENT_MDTM
108 NULL
,/* &Ftp::Relay::readReply */ // SENT_SIZE
111 &Ftp::Relay::readEpsvReply
, // SENT_EPSV_ALL
112 &Ftp::Relay::readEpsvReply
, // SENT_EPSV_1
113 &Ftp::Relay::readEpsvReply
, // SENT_EPSV_2
114 &Ftp::Relay::readPasvReply
, // SENT_PASV
115 &Ftp::Relay::readCwdOrCdupReply
, // SENT_CWD
116 NULL
,/* &Ftp::Relay::readDataReply, */ // SENT_LIST
117 NULL
,/* &Ftp::Relay::readDataReply, */ // SENT_NLST
118 NULL
,/* &Ftp::Relay::readReply */ // SENT_REST
119 NULL
,/* &Ftp::Relay::readDataReply */ // SENT_RETR
120 NULL
,/* &Ftp::Relay::readReply */ // SENT_STOR
121 NULL
,/* &Ftp::Relay::readReply */ // SENT_QUIT
122 &Ftp::Relay::readTransferDoneReply
, // READING_DATA
123 &Ftp::Relay::readReply
, // WRITING_DATA
124 NULL
,/* &Ftp::Relay::readReply */ // SENT_MKDIR
125 &Ftp::Relay::readFeatReply
, // SENT_FEAT
126 NULL
,/* &Ftp::Relay::readPwdReply */ // SENT_PWD
127 &Ftp::Relay::readCwdOrCdupReply
, // SENT_CDUP
128 &Ftp::Relay::readDataReply
,// SENT_DATA_REQUEST
129 &Ftp::Relay::readReply
, // SENT_COMMAND
133 Ftp::Relay::Relay(FwdState
*const fwdState
):
134 AsyncJob("Ftp::Relay"),
135 Ftp::Client(fwdState
),
136 forwardingCompleted(false)
138 savedReply
.message
= NULL
;
139 savedReply
.lastCommand
= NULL
;
140 savedReply
.lastReply
= NULL
;
141 savedReply
.replyCode
= 0;
143 // Nothing we can do at request creation time can mark the response as
144 // uncachable, unfortunately. This prevents "found KEY_PRIVATE" WARNINGs.
145 entry
->releaseRequest();
150 closeServer(); // TODO: move to Server.cc?
151 if (savedReply
.message
)
152 wordlistDestroy(&savedReply
.message
);
154 xfree(savedReply
.lastCommand
);
155 xfree(savedReply
.lastReply
);
161 if (!master().clientReadGreeting
)
162 Ftp::Client::start();
163 else if (serverState() == fssHandleDataRequest
||
164 serverState() == fssHandleUploadRequest
)
170 /// Keep control connection for future requests, after we are done with it.
171 /// Similar to COMPLETE_PERSISTENT_MSG handling in http.cc.
173 Ftp::Relay::serverComplete()
175 CbcPointer
<ConnStateData
> &mgr
= fwd
->request
->clientConnectionManager
;
177 if (Comm::IsConnOpen(ctrl
.conn
)) {
178 debugs(9, 7, "completing FTP server " << ctrl
.conn
<<
179 " after " << ctrl
.replycode
);
180 fwd
->unregister(ctrl
.conn
);
181 if (ctrl
.replycode
== 221) { // Server sends FTP 221 before closing
182 mgr
->unpinConnection(false);
185 mgr
->pinConnection(ctrl
.conn
, fwd
->request
,
186 ctrl
.conn
->getPeer(),
187 fwd
->request
->flags
.connectionAuth
);
192 Ftp::Client::serverComplete();
195 /// Safely returns the master state,
196 /// with safety checks in case the Ftp::Server side of the master xact is gone.
198 Ftp::Relay::updateMaster()
200 CbcPointer
<ConnStateData
> &mgr
= fwd
->request
->clientConnectionManager
;
202 if (Ftp::Server
*srv
= dynamic_cast<Ftp::Server
*>(mgr
.get()))
205 // this code will not be necessary once the master is inside MasterXaction
206 debugs(9, 3, "our server side is gone: " << mgr
);
207 static Ftp::MasterState Master
;
208 Master
= Ftp::MasterState();
212 /// A const variant of updateMaster().
213 const Ftp::MasterState
&
214 Ftp::Relay::master() const
216 return const_cast<Ftp::Relay
*>(this)->updateMaster(); // avoid code dupe
219 /// Changes server state and debugs about that important event.
221 Ftp::Relay::serverState(const Ftp::ServerState newState
)
223 Ftp::ServerState
&cltState
= updateMaster().serverState
;
224 debugs(9, 3, "client state was " << cltState
<< " now: " << newState
);
229 * Ensure we do not double-complete on the forward entry.
230 * We complete forwarding when the response adaptation is over
231 * (but we may still be waiting for 226 from the FTP server) and
232 * also when we get that 226 from the server (and adaptation is done).
234 \todo Rewrite FwdState to ignore double completion?
237 Ftp::Relay::completeForwarding()
239 debugs(9, 5, forwardingCompleted
);
240 if (forwardingCompleted
)
242 forwardingCompleted
= true;
243 Ftp::Client::completeForwarding();
247 Ftp::Relay::failed(err_type error
, int xerrno
)
249 if (!doneWithServer())
250 serverState(fssError
);
252 // TODO: we need to customize ErrorState instead
253 if (entry
->isEmpty())
254 failedErrorMessage(error
, xerrno
); // as a reply
256 Ftp::Client::failed(error
, xerrno
);
260 Ftp::Relay::failedErrorMessage(err_type error
, int xerrno
)
262 const Http::StatusCode httpStatus
= failedHttpStatus(error
);
263 HttpReply
*const reply
= createHttpReply(httpStatus
);
264 entry
->replaceHttpReply(reply
);
265 EBIT_CLR(entry
->flags
, ENTRY_FWD_HDR_WAIT
);
266 fwd
->request
->detailError(error
, xerrno
);
270 Ftp::Relay::processReplyBody()
272 debugs(9, 3, status());
274 if (EBIT_TEST(entry
->flags
, ENTRY_ABORTED
)) {
276 * probably was aborted because content length exceeds one
277 * of the maximum size limits.
279 abortTransaction("entry aborted after calling appendSuccessHeader()");
285 if (adaptationAccessCheckPending
) {
286 debugs(9, 3, "returning due to adaptationAccessCheckPending");
292 if (data
.readBuf
!= NULL
&& data
.readBuf
->hasContent()) {
293 const mb_size_t csize
= data
.readBuf
->contentSize();
294 debugs(9, 5, "writing " << csize
<< " bytes to the reply");
295 addVirginReplyBody(data
.readBuf
->content(), csize
);
296 data
.readBuf
->consume(csize
);
301 maybeReadVirginBody();
305 Ftp::Relay::handleControlReply()
307 if (!request
->clientConnectionManager
.valid()) {
308 debugs(9, 5, "client connection gone");
313 Ftp::Client::handleControlReply();
314 if (ctrl
.message
== NULL
)
315 return; // didn't get complete reply yet
318 assert(this->SM_FUNCS
[state
] != NULL
);
319 (this->*SM_FUNCS
[state
])();
323 Ftp::Relay::handleRequestBodyProducerAborted()
325 ::ServerStateData::handleRequestBodyProducerAborted();
327 failed(ERR_READ_ERROR
);
331 Ftp::Relay::mayReadVirginReplyBody() const
333 // TODO: move this method to the regular FTP server?
334 return Comm::IsConnOpen(data
.conn
);
338 Ftp::Relay::forwardReply()
340 assert(entry
->isEmpty());
341 EBIT_CLR(entry
->flags
, ENTRY_FWD_HDR_WAIT
);
343 HttpReply
*const reply
= createHttpReply(Http::scNoContent
);
345 setVirginReply(reply
);
346 adaptOrFinalizeReply();
352 Ftp::Relay::forwardPreliminaryReply(const PreliminaryCb cb
)
354 debugs(9, 5, "forwarding preliminary reply to client");
356 // we must prevent concurrent ConnStateData::sendControlMsg() calls
357 Must(thePreliminaryCb
== NULL
);
358 thePreliminaryCb
= cb
;
360 const HttpReply::Pointer reply
= createHttpReply(Http::scContinue
);
362 // the Sink will use this to call us back after writing 1xx to the client
363 typedef NullaryMemFunT
<Relay
> CbDialer
;
364 const AsyncCall::Pointer call
= JobCallback(11, 3, CbDialer
, this,
365 Ftp::Relay::proceedAfterPreliminaryReply
);
367 CallJobHere1(9, 4, request
->clientConnectionManager
, ConnStateData
,
368 ConnStateData::sendControlMsg
, HttpControlMsg(reply
, call
));
372 Ftp::Relay::proceedAfterPreliminaryReply()
374 debugs(9, 5, "proceeding after preliminary reply to client");
376 Must(thePreliminaryCb
!= NULL
);
377 const PreliminaryCb cb
= thePreliminaryCb
;
378 thePreliminaryCb
= NULL
;
383 Ftp::Relay::forwardError(err_type error
, int xerrno
)
385 failed(error
, xerrno
);
389 Ftp::Relay::createHttpReply(const Http::StatusCode httpStatus
, const int64_t clen
)
391 HttpReply
*const reply
= Ftp::HttpReplyWrapper(ctrl
.replycode
, ctrl
.last_reply
, httpStatus
, clen
);
393 for (wordlist
*W
= ctrl
.message
; W
&& W
->next
; W
= W
->next
)
394 reply
->header
.putStr(HDR_FTP_PRE
, httpHeaderQuoteString(W
->key
).c_str());
395 // no hdrCacheInit() is needed for after HDR_FTP_PRE addition
401 Ftp::Relay::handleDataRequest()
403 data
.addr(master().clientDataAddr
);
404 connectDataChannel();
408 Ftp::Relay::startDataDownload()
410 assert(Comm::IsConnOpen(data
.conn
));
412 debugs(9, 3, "begin data transfer from " << data
.conn
->remote
<<
413 " (" << data
.conn
->local
<< ")");
415 HttpReply
*const reply
= createHttpReply(Http::scOkay
, -1);
416 EBIT_CLR(entry
->flags
, ENTRY_FWD_HDR_WAIT
);
417 setVirginReply(reply
);
418 adaptOrFinalizeReply();
420 maybeReadVirginBody();
421 state
= READING_DATA
;
425 Ftp::Relay::startDataUpload()
427 assert(Comm::IsConnOpen(data
.conn
));
429 debugs(9, 3, "begin data transfer to " << data
.conn
->remote
<<
430 " (" << data
.conn
->local
<< ")");
432 if (!startRequestBodyFlow()) { // register to receive body data
437 state
= WRITING_DATA
;
441 Ftp::Relay::readGreeting()
443 assert(!master().clientReadGreeting
);
445 switch (ctrl
.replycode
) {
447 updateMaster().clientReadGreeting
= true;
448 if (serverState() == fssBegin
)
449 serverState(fssConnected
);
451 // Do not forward server greeting to the user because our FTP Server
452 // has greeted the user already. Also, an original origin greeting may
453 // confuse a user that has changed the origin mid-air.
458 if (NULL
!= ctrl
.message
)
459 debugs(9, DBG_IMPORTANT
, "FTP server is busy: " << ctrl
.message
->key
);
460 forwardPreliminaryReply(&Ftp::Relay::scheduleReadControlReply
);
469 Ftp::Relay::sendCommand()
471 if (!fwd
->request
->header
.has(HDR_FTP_COMMAND
)) {
472 abortTransaction("Internal error: FTP relay request with no command");
476 HttpHeader
&header
= fwd
->request
->header
;
477 assert(header
.has(HDR_FTP_COMMAND
));
478 const String
&cmd
= header
.findEntry(HDR_FTP_COMMAND
)->value
;
479 assert(header
.has(HDR_FTP_ARGUMENTS
));
480 const String
¶ms
= header
.findEntry(HDR_FTP_ARGUMENTS
)->value
;
482 if (params
.size() > 0)
483 debugs(9, 5, "command: " << cmd
<< ", parameters: " << params
);
485 debugs(9, 5, "command: " << cmd
<< ", no parameters");
487 if (serverState() == fssHandlePasv
||
488 serverState() == fssHandleEpsv
||
489 serverState() == fssHandleEprt
||
490 serverState() == fssHandlePort
) {
496 if (params
.size() > 0)
497 buf
.Printf("%s %s%s", cmd
.termedBuf(), params
.termedBuf(), Ftp::crlf
);
499 buf
.Printf("%s%s", cmd
.termedBuf(), Ftp::crlf
);
501 writeCommand(buf
.c_str());
504 serverState() == fssHandleCdup
? SENT_CDUP
:
505 serverState() == fssHandleCwd
? SENT_CWD
:
506 serverState() == fssHandleFeat
? SENT_FEAT
:
507 serverState() == fssHandleDataRequest
? SENT_DATA_REQUEST
:
508 serverState() == fssHandleUploadRequest
? SENT_DATA_REQUEST
:
509 serverState() == fssConnected
? SENT_USER
:
510 serverState() == fssHandlePass
? SENT_PASS
:
515 Ftp::Relay::readReply()
517 assert(serverState() == fssConnected
||
518 serverState() == fssHandleUploadRequest
);
520 if (100 <= ctrl
.replycode
&& ctrl
.replycode
< 200)
521 forwardPreliminaryReply(&Ftp::Relay::scheduleReadControlReply
);
527 Ftp::Relay::readFeatReply()
529 assert(serverState() == fssHandleFeat
);
531 if (100 <= ctrl
.replycode
&& ctrl
.replycode
< 200)
532 return; // ignore preliminary replies
538 Ftp::Relay::readPasvReply()
540 assert(serverState() == fssHandlePasv
|| serverState() == fssHandleEpsv
|| serverState() == fssHandlePort
|| serverState() == fssHandleEprt
);
542 if (100 <= ctrl
.replycode
&& ctrl
.replycode
< 200)
543 return; // ignore preliminary replies
545 if (handlePasvReply(updateMaster().clientDataAddr
))
552 Ftp::Relay::readEpsvReply()
554 if (100 <= ctrl
.replycode
&& ctrl
.replycode
< 200)
555 return; // ignore preliminary replies
557 if (handleEpsvReply(updateMaster().clientDataAddr
)) {
558 if (ctrl
.message
== NULL
)
559 return; // didn't get complete reply yet
567 Ftp::Relay::readDataReply()
569 assert(serverState() == fssHandleDataRequest
||
570 serverState() == fssHandleUploadRequest
);
572 if (ctrl
.replycode
== 125 || ctrl
.replycode
== 150) {
573 if (serverState() == fssHandleDataRequest
)
574 forwardPreliminaryReply(&Ftp::Relay::startDataDownload
);
575 else // serverState() == fssHandleUploadRequest
576 forwardPreliminaryReply(&Ftp::Relay::startDataUpload
);
582 Ftp::Relay::startDirTracking()
584 if (!fwd
->request
->clientConnectionManager
->port
->ftp_track_dirs
)
587 debugs(9, 5, "start directory tracking");
588 savedReply
.message
= ctrl
.message
;
589 savedReply
.lastCommand
= ctrl
.last_command
;
590 savedReply
.lastReply
= ctrl
.last_reply
;
591 savedReply
.replyCode
= ctrl
.replycode
;
593 ctrl
.last_command
= NULL
;
594 ctrl
.last_reply
= NULL
;
597 writeCommand("PWD\r\n");
602 Ftp::Relay::stopDirTracking()
604 debugs(9, 5, "got code from pwd: " << ctrl
.replycode
<< ", msg: " << ctrl
.last_reply
);
606 if (ctrl
.replycode
== 257)
607 updateMaster().workingDir
= Ftp::UnescapeDoubleQuoted(ctrl
.last_reply
);
609 wordlistDestroy(&ctrl
.message
);
610 safe_free(ctrl
.last_command
);
611 safe_free(ctrl
.last_reply
);
613 ctrl
.message
= savedReply
.message
;
614 ctrl
.last_command
= savedReply
.lastCommand
;
615 ctrl
.last_reply
= savedReply
.lastReply
;
616 ctrl
.replycode
= savedReply
.replyCode
;
618 savedReply
.message
= NULL
;
619 savedReply
.lastReply
= NULL
;
620 savedReply
.lastCommand
= NULL
;
624 Ftp::Relay::readCwdOrCdupReply()
626 assert(serverState() == fssHandleCwd
||
627 serverState() == fssHandleCdup
);
629 debugs(9, 5, "got code " << ctrl
.replycode
<< ", msg: " << ctrl
.last_reply
);
631 if (100 <= ctrl
.replycode
&& ctrl
.replycode
< 200)
634 if (weAreTrackingDir()) { // we are tracking
635 stopDirTracking(); // and forward the delayed response below
636 } else if (startDirTracking())
643 Ftp::Relay::readUserOrPassReply()
645 if (100 <= ctrl
.replycode
&& ctrl
.replycode
< 200)
646 return; //Just ignore
648 if (weAreTrackingDir()) { // we are tracking
649 stopDirTracking(); // and forward the delayed response below
650 } else if (ctrl
.replycode
== 230) { // successful login
651 if (startDirTracking())
659 Ftp::Relay::readTransferDoneReply()
661 debugs(9, 3, status());
663 if (ctrl
.replycode
!= 226 && ctrl
.replycode
!= 250) {
664 debugs(9, DBG_IMPORTANT
, "got FTP code " << ctrl
.replycode
<<
665 " after reading response data");
672 Ftp::Relay::dataChannelConnected(const CommConnectCbParams
&io
)
674 debugs(9, 3, status());
677 if (io
.flag
!= Comm::OK
) {
678 debugs(9, 2, "failed to connect FTP server data channel");
679 forwardError(ERR_CONNECT_FAIL
, io
.xerrno
);
683 debugs(9, 2, "connected FTP server data channel: " << io
.conn
);
685 data
.opened(io
.conn
, dataCloser());
691 Ftp::Relay::scheduleReadControlReply()
693 Ftp::Client::scheduleReadControlReply(0);
697 Ftp::StartRelay(FwdState
*const fwdState
)
699 return AsyncJob::Start(new Ftp::Relay(fwdState
));