2 * Copyright (C) 1996-2014 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 "HttpHdrCc.h"
19 #include "HttpRequest.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
36 explicit Relay(FwdState
*const fwdState
);
40 const Ftp::MasterState
&master() const;
41 Ftp::MasterState
&updateMaster();
42 Ftp::ServerState
serverState() const { return master().serverState
; }
43 void serverState(const Ftp::ServerState newState
);
46 virtual void failed(err_type error
= ERR_NONE
, int xerrno
= 0);
47 virtual void dataChannelConnected(const CommConnectCbParams
&io
);
49 /* ServerStateData API */
50 virtual void serverComplete();
51 virtual void handleControlReply();
52 virtual void processReplyBody();
53 virtual void handleRequestBodyProducerAborted();
54 virtual bool mayReadVirginReplyBody() const;
55 virtual void completeForwarding();
61 void forwardError(err_type error
= ERR_NONE
, int xerrno
= 0);
62 void failedErrorMessage(err_type error
, int xerrno
);
63 HttpReply
*createHttpReply(const Http::StatusCode httpStatus
, const int64_t clen
= 0);
64 void handleDataRequest();
65 void startDataDownload();
66 void startDataUpload();
67 bool startDirTracking();
68 void stopDirTracking();
69 bool weAreTrackingDir() const {return savedReply
.message
!= NULL
;}
71 typedef void (Relay::*PreliminaryCb
)();
72 void forwardPreliminaryReply(const PreliminaryCb cb
);
73 void proceedAfterPreliminaryReply();
74 PreliminaryCb thePreliminaryCb
;
76 typedef void (Relay::*SM_FUNC
)();
77 static const SM_FUNC SM_FUNCS
[];
84 void readTransferDoneReply();
86 void readCwdOrCdupReply();
87 void readUserOrPassReply();
89 void scheduleReadControlReply();
91 bool forwardingCompleted
; ///< completeForwarding() has been called
94 wordlist
*message
; ///< reply message, one wordlist entry per message line
95 char *lastCommand
; ///< the command caused the reply
96 char *lastReply
; ///< last line of reply: reply status plus message
97 int replyCode
; ///< the reply status
98 } savedReply
; ///< set and delayed while we are tracking using PWD
100 CBDATA_CLASS2(Relay
);
105 CBDATA_NAMESPACED_CLASS_INIT(Ftp
, Relay
);
107 const Ftp::Relay::SM_FUNC
Ftp::Relay::SM_FUNCS
[] = {
108 &Ftp::Relay::readGreeting
, // BEGIN
109 &Ftp::Relay::readUserOrPassReply
, // SENT_USER
110 &Ftp::Relay::readUserOrPassReply
, // SENT_PASS
111 NULL
,/* &Ftp::Relay::readReply */ // SENT_TYPE
112 NULL
,/* &Ftp::Relay::readReply */ // SENT_MDTM
113 NULL
,/* &Ftp::Relay::readReply */ // SENT_SIZE
116 &Ftp::Relay::readEpsvReply
, // SENT_EPSV_ALL
117 &Ftp::Relay::readEpsvReply
, // SENT_EPSV_1
118 &Ftp::Relay::readEpsvReply
, // SENT_EPSV_2
119 &Ftp::Relay::readPasvReply
, // SENT_PASV
120 &Ftp::Relay::readCwdOrCdupReply
, // SENT_CWD
121 NULL
,/* &Ftp::Relay::readDataReply, */ // SENT_LIST
122 NULL
,/* &Ftp::Relay::readDataReply, */ // SENT_NLST
123 NULL
,/* &Ftp::Relay::readReply */ // SENT_REST
124 NULL
,/* &Ftp::Relay::readDataReply */ // SENT_RETR
125 NULL
,/* &Ftp::Relay::readReply */ // SENT_STOR
126 NULL
,/* &Ftp::Relay::readReply */ // SENT_QUIT
127 &Ftp::Relay::readTransferDoneReply
, // READING_DATA
128 &Ftp::Relay::readReply
, // WRITING_DATA
129 NULL
,/* &Ftp::Relay::readReply */ // SENT_MKDIR
130 &Ftp::Relay::readFeatReply
, // SENT_FEAT
131 NULL
,/* &Ftp::Relay::readPwdReply */ // SENT_PWD
132 &Ftp::Relay::readCwdOrCdupReply
, // SENT_CDUP
133 &Ftp::Relay::readDataReply
,// SENT_DATA_REQUEST
134 &Ftp::Relay::readReply
, // SENT_COMMAND
138 Ftp::Relay::Relay(FwdState
*const fwdState
):
139 AsyncJob("Ftp::Relay"),
140 Ftp::Client(fwdState
),
141 forwardingCompleted(false)
143 savedReply
.message
= NULL
;
144 savedReply
.lastCommand
= NULL
;
145 savedReply
.lastReply
= NULL
;
146 savedReply
.replyCode
= 0;
148 // Nothing we can do at request creation time can mark the response as
149 // uncachable, unfortunately. This prevents "found KEY_PRIVATE" WARNINGs.
150 entry
->releaseRequest();
155 closeServer(); // TODO: move to Server.cc?
156 if (savedReply
.message
)
157 wordlistDestroy(&savedReply
.message
);
159 xfree(savedReply
.lastCommand
);
160 xfree(savedReply
.lastReply
);
166 if (!master().clientReadGreeting
)
167 Ftp::Client::start();
168 else if (serverState() == fssHandleDataRequest
||
169 serverState() == fssHandleUploadRequest
)
175 /// Keep control connection for future requests, after we are done with it.
176 /// Similar to COMPLETE_PERSISTENT_MSG handling in http.cc.
178 Ftp::Relay::serverComplete()
180 CbcPointer
<ConnStateData
> &mgr
= fwd
->request
->clientConnectionManager
;
182 if (Comm::IsConnOpen(ctrl
.conn
)) {
183 debugs(9, 7, "completing FTP server " << ctrl
.conn
<<
184 " after " << ctrl
.replycode
);
185 fwd
->unregister(ctrl
.conn
);
186 if (ctrl
.replycode
== 221) { // Server sends FTP 221 before closing
187 mgr
->unpinConnection(false);
190 mgr
->pinConnection(ctrl
.conn
, fwd
->request
,
191 ctrl
.conn
->getPeer(),
192 fwd
->request
->flags
.connectionAuth
);
197 Ftp::Client::serverComplete();
200 /// Safely returns the master state,
201 /// with safety checks in case the Ftp::Server side of the master xact is gone.
203 Ftp::Relay::updateMaster()
205 CbcPointer
<ConnStateData
> &mgr
= fwd
->request
->clientConnectionManager
;
207 if (Ftp::Server
*srv
= dynamic_cast<Ftp::Server
*>(mgr
.get()))
210 // this code will not be necessary once the master is inside MasterXaction
211 debugs(9, 3, "our server side is gone: " << mgr
);
212 static Ftp::MasterState Master
;
213 Master
= Ftp::MasterState();
217 /// A const variant of updateMaster().
218 const Ftp::MasterState
&
219 Ftp::Relay::master() const
221 return const_cast<Ftp::Relay
*>(this)->updateMaster(); // avoid code dupe
224 /// Changes server state and debugs about that important event.
226 Ftp::Relay::serverState(const Ftp::ServerState newState
)
228 Ftp::ServerState
&cltState
= updateMaster().serverState
;
229 debugs(9, 3, "client state was " << cltState
<< " now: " << newState
);
234 * Ensure we do not double-complete on the forward entry.
235 * We complete forwarding when the response adaptation is over
236 * (but we may still be waiting for 226 from the FTP server) and
237 * also when we get that 226 from the server (and adaptation is done).
239 \todo Rewrite FwdState to ignore double completion?
242 Ftp::Relay::completeForwarding()
244 debugs(9, 5, forwardingCompleted
);
245 if (forwardingCompleted
)
247 forwardingCompleted
= true;
248 Ftp::Client::completeForwarding();
252 Ftp::Relay::failed(err_type error
, int xerrno
)
254 if (!doneWithServer())
255 serverState(fssError
);
257 // TODO: we need to customize ErrorState instead
258 if (entry
->isEmpty())
259 failedErrorMessage(error
, xerrno
); // as a reply
261 Ftp::Client::failed(error
, xerrno
);
265 Ftp::Relay::failedErrorMessage(err_type error
, int xerrno
)
267 const Http::StatusCode httpStatus
= failedHttpStatus(error
);
268 HttpReply
*const reply
= createHttpReply(httpStatus
);
269 entry
->replaceHttpReply(reply
);
270 EBIT_CLR(entry
->flags
, ENTRY_FWD_HDR_WAIT
);
271 fwd
->request
->detailError(error
, xerrno
);
275 Ftp::Relay::processReplyBody()
277 debugs(9, 3, status());
279 if (EBIT_TEST(entry
->flags
, ENTRY_ABORTED
)) {
281 * probably was aborted because content length exceeds one
282 * of the maximum size limits.
284 abortTransaction("entry aborted after calling appendSuccessHeader()");
290 if (adaptationAccessCheckPending
) {
291 debugs(9, 3, "returning due to adaptationAccessCheckPending");
297 if (data
.readBuf
!= NULL
&& data
.readBuf
->hasContent()) {
298 const mb_size_t csize
= data
.readBuf
->contentSize();
299 debugs(9, 5, "writing " << csize
<< " bytes to the reply");
300 addVirginReplyBody(data
.readBuf
->content(), csize
);
301 data
.readBuf
->consume(csize
);
306 maybeReadVirginBody();
310 Ftp::Relay::handleControlReply()
312 if (!request
->clientConnectionManager
.valid()) {
313 debugs(9, 5, "client connection gone");
318 Ftp::Client::handleControlReply();
319 if (ctrl
.message
== NULL
)
320 return; // didn't get complete reply yet
323 assert(this->SM_FUNCS
[state
] != NULL
);
324 (this->*SM_FUNCS
[state
])();
328 Ftp::Relay::handleRequestBodyProducerAborted()
330 ::ServerStateData::handleRequestBodyProducerAborted();
332 failed(ERR_READ_ERROR
);
336 Ftp::Relay::mayReadVirginReplyBody() const
338 // TODO: move this method to the regular FTP server?
339 return Comm::IsConnOpen(data
.conn
);
343 Ftp::Relay::forwardReply()
345 assert(entry
->isEmpty());
346 EBIT_CLR(entry
->flags
, ENTRY_FWD_HDR_WAIT
);
348 HttpReply
*const reply
= createHttpReply(Http::scNoContent
);
350 setVirginReply(reply
);
351 adaptOrFinalizeReply();
357 Ftp::Relay::forwardPreliminaryReply(const PreliminaryCb cb
)
359 debugs(9, 5, "forwarding preliminary reply to client");
361 // we must prevent concurrent ConnStateData::sendControlMsg() calls
362 Must(thePreliminaryCb
== NULL
);
363 thePreliminaryCb
= cb
;
365 const HttpReply::Pointer reply
= createHttpReply(Http::scContinue
);
367 // the Sink will use this to call us back after writing 1xx to the client
368 typedef NullaryMemFunT
<Relay
> CbDialer
;
369 const AsyncCall::Pointer call
= JobCallback(11, 3, CbDialer
, this,
370 Ftp::Relay::proceedAfterPreliminaryReply
);
372 CallJobHere1(9, 4, request
->clientConnectionManager
, ConnStateData
,
373 ConnStateData::sendControlMsg
, HttpControlMsg(reply
, call
));
377 Ftp::Relay::proceedAfterPreliminaryReply()
379 debugs(9, 5, "proceeding after preliminary reply to client");
381 Must(thePreliminaryCb
!= NULL
);
382 const PreliminaryCb cb
= thePreliminaryCb
;
383 thePreliminaryCb
= NULL
;
388 Ftp::Relay::forwardError(err_type error
, int xerrno
)
390 failed(error
, xerrno
);
394 Ftp::Relay::createHttpReply(const Http::StatusCode httpStatus
, const int64_t clen
)
396 HttpReply
*const reply
= Ftp::HttpReplyWrapper(ctrl
.replycode
, ctrl
.last_reply
, httpStatus
, clen
);
398 for (wordlist
*W
= ctrl
.message
; W
&& W
->next
; W
= W
->next
)
399 reply
->header
.putStr(HDR_FTP_PRE
, httpHeaderQuoteString(W
->key
).c_str());
400 // no hdrCacheInit() is needed for after HDR_FTP_PRE addition
406 Ftp::Relay::handleDataRequest()
408 data
.addr(master().clientDataAddr
);
409 connectDataChannel();
413 Ftp::Relay::startDataDownload()
415 assert(Comm::IsConnOpen(data
.conn
));
417 debugs(9, 3, "begin data transfer from " << data
.conn
->remote
<<
418 " (" << data
.conn
->local
<< ")");
420 HttpReply
*const reply
= createHttpReply(Http::scOkay
, -1);
421 EBIT_CLR(entry
->flags
, ENTRY_FWD_HDR_WAIT
);
422 setVirginReply(reply
);
423 adaptOrFinalizeReply();
425 maybeReadVirginBody();
426 state
= READING_DATA
;
430 Ftp::Relay::startDataUpload()
432 assert(Comm::IsConnOpen(data
.conn
));
434 debugs(9, 3, "begin data transfer to " << data
.conn
->remote
<<
435 " (" << data
.conn
->local
<< ")");
437 if (!startRequestBodyFlow()) { // register to receive body data
442 state
= WRITING_DATA
;
446 Ftp::Relay::readGreeting()
448 assert(!master().clientReadGreeting
);
450 switch (ctrl
.replycode
) {
452 updateMaster().clientReadGreeting
= true;
453 if (serverState() == fssBegin
)
454 serverState(fssConnected
);
456 // Do not forward server greeting to the user because our FTP Server
457 // has greeted the user already. Also, an original origin greeting may
458 // confuse a user that has changed the origin mid-air.
463 if (NULL
!= ctrl
.message
)
464 debugs(9, DBG_IMPORTANT
, "FTP server is busy: " << ctrl
.message
->key
);
465 forwardPreliminaryReply(&Ftp::Relay::scheduleReadControlReply
);
474 Ftp::Relay::sendCommand()
476 if (!fwd
->request
->header
.has(HDR_FTP_COMMAND
)) {
477 abortTransaction("Internal error: FTP relay request with no command");
481 HttpHeader
&header
= fwd
->request
->header
;
482 assert(header
.has(HDR_FTP_COMMAND
));
483 const String
&cmd
= header
.findEntry(HDR_FTP_COMMAND
)->value
;
484 assert(header
.has(HDR_FTP_ARGUMENTS
));
485 const String
¶ms
= header
.findEntry(HDR_FTP_ARGUMENTS
)->value
;
487 if (params
.size() > 0)
488 debugs(9, 5, "command: " << cmd
<< ", parameters: " << params
);
490 debugs(9, 5, "command: " << cmd
<< ", no parameters");
492 if (serverState() == fssHandlePasv
||
493 serverState() == fssHandleEpsv
||
494 serverState() == fssHandleEprt
||
495 serverState() == fssHandlePort
) {
501 if (params
.size() > 0)
502 buf
.Printf("%s %s%s", cmd
.termedBuf(), params
.termedBuf(), Ftp::crlf
);
504 buf
.Printf("%s%s", cmd
.termedBuf(), Ftp::crlf
);
506 writeCommand(buf
.c_str());
509 serverState() == fssHandleCdup
? SENT_CDUP
:
510 serverState() == fssHandleCwd
? SENT_CWD
:
511 serverState() == fssHandleFeat
? SENT_FEAT
:
512 serverState() == fssHandleDataRequest
? SENT_DATA_REQUEST
:
513 serverState() == fssHandleUploadRequest
? SENT_DATA_REQUEST
:
514 serverState() == fssConnected
? SENT_USER
:
515 serverState() == fssHandlePass
? SENT_PASS
:
520 Ftp::Relay::readReply()
522 assert(serverState() == fssConnected
||
523 serverState() == fssHandleUploadRequest
);
525 if (100 <= ctrl
.replycode
&& ctrl
.replycode
< 200)
526 forwardPreliminaryReply(&Ftp::Relay::scheduleReadControlReply
);
532 Ftp::Relay::readFeatReply()
534 assert(serverState() == fssHandleFeat
);
536 if (100 <= ctrl
.replycode
&& ctrl
.replycode
< 200)
537 return; // ignore preliminary replies
543 Ftp::Relay::readPasvReply()
545 assert(serverState() == fssHandlePasv
|| serverState() == fssHandleEpsv
|| serverState() == fssHandlePort
|| serverState() == fssHandleEprt
);
547 if (100 <= ctrl
.replycode
&& ctrl
.replycode
< 200)
548 return; // ignore preliminary replies
550 if (handlePasvReply(updateMaster().clientDataAddr
))
557 Ftp::Relay::readEpsvReply()
559 if (100 <= ctrl
.replycode
&& ctrl
.replycode
< 200)
560 return; // ignore preliminary replies
562 if (handleEpsvReply(updateMaster().clientDataAddr
)) {
563 if (ctrl
.message
== NULL
)
564 return; // didn't get complete reply yet
572 Ftp::Relay::readDataReply()
574 assert(serverState() == fssHandleDataRequest
||
575 serverState() == fssHandleUploadRequest
);
577 if (ctrl
.replycode
== 125 || ctrl
.replycode
== 150) {
578 if (serverState() == fssHandleDataRequest
)
579 forwardPreliminaryReply(&Ftp::Relay::startDataDownload
);
580 else // serverState() == fssHandleUploadRequest
581 forwardPreliminaryReply(&Ftp::Relay::startDataUpload
);
587 Ftp::Relay::startDirTracking()
589 if (!fwd
->request
->clientConnectionManager
->port
->ftp_track_dirs
)
592 debugs(9, 5, "start directory tracking");
593 savedReply
.message
= ctrl
.message
;
594 savedReply
.lastCommand
= ctrl
.last_command
;
595 savedReply
.lastReply
= ctrl
.last_reply
;
596 savedReply
.replyCode
= ctrl
.replycode
;
598 ctrl
.last_command
= NULL
;
599 ctrl
.last_reply
= NULL
;
602 writeCommand("PWD\r\n");
607 Ftp::Relay::stopDirTracking()
609 debugs(9, 5, "got code from pwd: " << ctrl
.replycode
<< ", msg: " << ctrl
.last_reply
);
611 if (ctrl
.replycode
== 257)
612 updateMaster().workingDir
= Ftp::UnescapeDoubleQuoted(ctrl
.last_reply
);
614 wordlistDestroy(&ctrl
.message
);
615 safe_free(ctrl
.last_command
);
616 safe_free(ctrl
.last_reply
);
618 ctrl
.message
= savedReply
.message
;
619 ctrl
.last_command
= savedReply
.lastCommand
;
620 ctrl
.last_reply
= savedReply
.lastReply
;
621 ctrl
.replycode
= savedReply
.replyCode
;
623 savedReply
.message
= NULL
;
624 savedReply
.lastReply
= NULL
;
625 savedReply
.lastCommand
= NULL
;
629 Ftp::Relay::readCwdOrCdupReply()
631 assert(serverState() == fssHandleCwd
||
632 serverState() == fssHandleCdup
);
634 debugs(9, 5, "got code " << ctrl
.replycode
<< ", msg: " << ctrl
.last_reply
);
636 if (100 <= ctrl
.replycode
&& ctrl
.replycode
< 200)
639 if (weAreTrackingDir()) { // we are tracking
640 stopDirTracking(); // and forward the delayed response below
641 } else if (startDirTracking())
648 Ftp::Relay::readUserOrPassReply()
650 if (100 <= ctrl
.replycode
&& ctrl
.replycode
< 200)
651 return; //Just ignore
653 if (weAreTrackingDir()) { // we are tracking
654 stopDirTracking(); // and forward the delayed response below
655 } else if (ctrl
.replycode
== 230) { // successful login
656 if (startDirTracking())
664 Ftp::Relay::readTransferDoneReply()
666 debugs(9, 3, status());
668 if (ctrl
.replycode
!= 226 && ctrl
.replycode
!= 250) {
669 debugs(9, DBG_IMPORTANT
, "got FTP code " << ctrl
.replycode
<<
670 " after reading response data");
677 Ftp::Relay::dataChannelConnected(const CommConnectCbParams
&io
)
679 debugs(9, 3, status());
682 if (io
.flag
!= Comm::OK
) {
683 debugs(9, 2, "failed to connect FTP server data channel");
684 forwardError(ERR_CONNECT_FAIL
, io
.xerrno
);
688 debugs(9, 2, "connected FTP server data channel: " << io
.conn
);
690 data
.opened(io
.conn
, dataCloser());
696 Ftp::Relay::scheduleReadControlReply()
698 Ftp::Client::scheduleReadControlReply(0);
702 Ftp::StartRelay(FwdState
*const fwdState
)
704 return AsyncJob::Start(new Ftp::Relay(fwdState
));