1 /*
2  * Copyright (C) 1996-2021 The Squid Software Foundation and contributors
3  *
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.
7  */
8 
9 /* DEBUG: section 09    File Transfer Protocol (FTP) */
10 
11 #include "squid.h"
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"
24 #include "Store.h"
25 #include "wordlist.h"
26 
27 namespace Ftp
28 {
29 
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
34 {
35     CBDATA_CLASS(Relay);
36 
37 public:
38     explicit Relay(FwdState *const fwdState);
39     virtual ~Relay();
40 
41 protected:
42     const Ftp::MasterState &master() const;
43     Ftp::MasterState &updateMaster();
serverState() const44     Ftp::ServerState serverState() const { return master().serverState; }
45     void serverState(const Ftp::ServerState newState);
46 
47     /* Ftp::Client API */
48     virtual void failed(err_type error = ERR_NONE, int xerrno = 0, ErrorState *ftperr = nullptr);
49     virtual void dataChannelConnected(const CommConnectCbParams &io);
50 
51     /* Client API */
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);
59 
60     /* AsyncJob API */
61     virtual void start();
62     virtual void swanSong();
63 
64     void forwardReply();
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();
weAreTrackingDir() const73     bool weAreTrackingDir() const {return savedReply.message != NULL;}
74 
75     typedef void (Relay::*PreliminaryCb)();
76     void forwardPreliminaryReply(const PreliminaryCb cb);
77     void proceedAfterPreliminaryReply();
78     PreliminaryCb thePreliminaryCb;
79 
80     typedef void (Relay::*SM_FUNC)();
81     static const SM_FUNC SM_FUNCS[];
82     void readGreeting();
83     void sendCommand();
84     void readReply();
85     void readFeatReply();
86     void readPasvReply();
87     void readDataReply();
88     void readTransferDoneReply();
89     void readEpsvReply();
90     void readCwdOrCdupReply();
91     void readUserOrPassReply();
92 
93     void scheduleReadControlReply();
94 
95     /// Inform Ftp::Server that we are done if originWaitInProgress
96     void stopOriginWait(int code);
97 
98     static void abort(void *d); // TODO: Capitalize this and FwdState::abort().
99 
100     bool forwardingCompleted; ///< completeForwarding() has been called
101 
102     /// whether we are between Ftp::Server::startWaitingForOrigin() and
103     /// Ftp::Server::stopWaitingForOrigin() calls
104     bool originWaitInProgress;
105 
106     struct {
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
112 };
113 
114 } // namespace Ftp
115 
116 CBDATA_NAMESPACED_CLASS_INIT(Ftp, Relay);
117 
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
125     NULL, // SENT_EPRT
126     NULL, // SENT_PORT
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
146     NULL
147 };
148 
Relay(FwdState * const fwdState)149 Ftp::Relay::Relay(FwdState *const fwdState):
150     AsyncJob("Ftp::Relay"),
151     Ftp::Client(fwdState),
152     thePreliminaryCb(NULL),
153     forwardingCompleted(false),
154     originWaitInProgress(false)
155 {
156     savedReply.message = NULL;
157     savedReply.lastCommand = NULL;
158     savedReply.lastReply = NULL;
159     savedReply.replyCode = 0;
160 
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);
166 }
167 
~Relay()168 Ftp::Relay::~Relay()
169 {
170     closeServer(); // TODO: move to clients/Client.cc?
171     if (savedReply.message)
172         wordlistDestroy(&savedReply.message);
173 
174     xfree(savedReply.lastCommand);
175     xfree(savedReply.lastReply);
176 }
177 
178 void
start()179 Ftp::Relay::start()
180 {
181     if (!master().clientReadGreeting)
182         Ftp::Client::start();
183     else if (serverState() == fssHandleDataRequest ||
184              serverState() == fssHandleUploadRequest)
185         handleDataRequest();
186     else
187         sendCommand();
188 }
189 
190 void
swanSong()191 Ftp::Relay::swanSong()
192 {
193     stopOriginWait(0);
194     Ftp::Client::swanSong();
195 }
196 
197 /// Keep control connection for future requests, after we are done with it.
198 /// Similar to COMPLETE_PERSISTENT_MSG handling in http.cc.
199 void
serverComplete()200 Ftp::Relay::serverComplete()
201 {
202     stopOriginWait(ctrl.replycode);
203 
204     CbcPointer<ConnStateData> &mgr = fwd->request->clientConnectionManager;
205     if (mgr.valid()) {
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);
212                 ctrl.close();
213             } else {
214                 CallJobHere1(9, 4, mgr,
215                              ConnStateData,
216                              notePinnedConnectionBecameIdle,
217                              ConnStateData::PinnedIdleContext(ctrl.conn, fwd->request));
218                 ctrl.forget();
219             }
220         }
221     }
222     Ftp::Client::serverComplete();
223 }
224 
225 /// Safely returns the master state,
226 /// with safety checks in case the Ftp::Server side of the master xact is gone.
227 Ftp::MasterState &
updateMaster()228 Ftp::Relay::updateMaster()
229 {
230     CbcPointer<ConnStateData> &mgr = fwd->request->clientConnectionManager;
231     if (mgr.valid()) {
232         if (Ftp::Server *srv = dynamic_cast<Ftp::Server*>(mgr.get()))
233             return *srv->master;
234     }
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();
239     return Master;
240 }
241 
242 /// A const variant of updateMaster().
243 const Ftp::MasterState &
master() const244 Ftp::Relay::master() const
245 {
246     return const_cast<Ftp::Relay*>(this)->updateMaster(); // avoid code dupe
247 }
248 
249 /// Changes server state and debugs about that important event.
250 void
serverState(const Ftp::ServerState newState)251 Ftp::Relay::serverState(const Ftp::ServerState newState)
252 {
253     Ftp::ServerState &cltState = updateMaster().serverState;
254     debugs(9, 3, "client state was " << cltState << " now: " << newState);
255     cltState = newState;
256 }
257 
258 /**
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).
263  *
264  \todo Rewrite FwdState to ignore double completion?
265  */
266 void
completeForwarding()267 Ftp::Relay::completeForwarding()
268 {
269     debugs(9, 5, forwardingCompleted);
270     if (forwardingCompleted)
271         return;
272     forwardingCompleted = true;
273     Ftp::Client::completeForwarding();
274 }
275 
276 void
failed(err_type error,int xerrno,ErrorState * ftpErr)277 Ftp::Relay::failed(err_type error, int xerrno, ErrorState *ftpErr)
278 {
279     if (!doneWithServer())
280         serverState(fssError);
281 
282     // TODO: we need to customize ErrorState instead
283     if (entry->isEmpty())
284         failedErrorMessage(error, xerrno); // as a reply
285 
286     Ftp::Client::failed(error, xerrno, ftpErr);
287 }
288 
289 void
failedErrorMessage(err_type error,int xerrno)290 Ftp::Relay::failedErrorMessage(err_type error, int xerrno)
291 {
292     const Http::StatusCode httpStatus = failedHttpStatus(error);
293     HttpReply *const reply = createHttpReply(httpStatus);
294     entry->replaceHttpReply(reply);
295     fwd->request->detailError(error, xerrno);
296 }
297 
298 void
processReplyBody()299 Ftp::Relay::processReplyBody()
300 {
301     debugs(9, 3, status());
302 
303     if (EBIT_TEST(entry->flags, ENTRY_ABORTED)) {
304         /*
305          * probably was aborted because content length exceeds one
306          * of the maximum size limits.
307          */
308         abortOnData("entry aborted after calling appendSuccessHeader()");
309         return;
310     }
311 
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");
316         return;
317     }
318 
319 #if USE_ADAPTATION
320 
321     if (adaptationAccessCheckPending) {
322         debugs(9, 3, "returning due to adaptationAccessCheckPending");
323         return;
324     }
325 
326 #endif
327 
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);
333     }
334 
335     entry->flush();
336 
337     maybeReadVirginBody();
338 }
339 
340 void
handleControlReply()341 Ftp::Relay::handleControlReply()
342 {
343     if (!request->clientConnectionManager.valid()) {
344         debugs(9, 5, "client connection gone");
345         closeServer();
346         return;
347     }
348 
349     Ftp::Client::handleControlReply();
350     if (ctrl.message == NULL)
351         return; // didn't get complete reply yet
352 
353     assert(state < END);
354     assert(this->SM_FUNCS[state] != NULL);
355     (this->*SM_FUNCS[state])();
356 }
357 
358 void
handleRequestBodyProducerAborted()359 Ftp::Relay::handleRequestBodyProducerAborted()
360 {
361     ::Client::handleRequestBodyProducerAborted();
362 
363     failed(ERR_READ_ERROR);
364 }
365 
366 bool
mayReadVirginReplyBody() const367 Ftp::Relay::mayReadVirginReplyBody() const
368 {
369     // TODO: move this method to the regular FTP server?
370     return Comm::IsConnOpen(data.conn);
371 }
372 
373 void
forwardReply()374 Ftp::Relay::forwardReply()
375 {
376     assert(entry->isEmpty());
377 
378     HttpReply *const reply = createHttpReply(Http::scNoContent);
379     reply->sources |= HttpMsg::srcFtp;
380 
381     setVirginReply(reply);
382     adaptOrFinalizeReply();
383 
384     serverComplete();
385 }
386 
387 void
forwardPreliminaryReply(const PreliminaryCb cb)388 Ftp::Relay::forwardPreliminaryReply(const PreliminaryCb cb)
389 {
390     debugs(9, 5, "forwarding preliminary reply to client");
391 
392     // we must prevent concurrent ConnStateData::sendControlMsg() calls
393     Must(thePreliminaryCb == NULL);
394     thePreliminaryCb = cb;
395 
396     const HttpReply::Pointer reply = createHttpReply(Http::scContinue);
397 
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);
402 
403     CallJobHere1(9, 4, request->clientConnectionManager, ConnStateData,
404                  ConnStateData::sendControlMsg, HttpControlMsg(reply, call));
405 }
406 
407 void
proceedAfterPreliminaryReply()408 Ftp::Relay::proceedAfterPreliminaryReply()
409 {
410     debugs(9, 5, "proceeding after preliminary reply to client");
411 
412     Must(thePreliminaryCb != NULL);
413     const PreliminaryCb cb = thePreliminaryCb;
414     thePreliminaryCb = NULL;
415     (this->*cb)();
416 }
417 
418 void
forwardError(err_type error,int xerrno)419 Ftp::Relay::forwardError(err_type error, int xerrno)
420 {
421     failed(error, xerrno);
422 }
423 
424 HttpReply *
createHttpReply(const Http::StatusCode httpStatus,const int64_t clen)425 Ftp::Relay::createHttpReply(const Http::StatusCode httpStatus, const int64_t clen)
426 {
427     HttpReply *const reply = Ftp::HttpReplyWrapper(ctrl.replycode, ctrl.last_reply, httpStatus, clen);
428     if (ctrl.message) {
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
432     }
433     return reply;
434 }
435 
436 void
handleDataRequest()437 Ftp::Relay::handleDataRequest()
438 {
439     data.addr(master().clientDataAddr);
440     connectDataChannel();
441 }
442 
443 void
startDataDownload()444 Ftp::Relay::startDataDownload()
445 {
446     assert(Comm::IsConnOpen(data.conn));
447 
448     debugs(9, 3, "begin data transfer from " << data.conn->remote <<
449            " (" << data.conn->local << ")");
450 
451     HttpReply *const reply = createHttpReply(Http::scOkay, -1);
452     reply->sources |= HttpMsg::srcFtp;
453 
454     setVirginReply(reply);
455     adaptOrFinalizeReply();
456 
457     maybeReadVirginBody();
458     state = READING_DATA;
459 }
460 
461 void
startDataUpload()462 Ftp::Relay::startDataUpload()
463 {
464     assert(Comm::IsConnOpen(data.conn));
465 
466     debugs(9, 3, "begin data transfer to " << data.conn->remote <<
467            " (" << data.conn->local << ")");
468 
469     if (!startRequestBodyFlow()) { // register to receive body data
470         failed();
471         return;
472     }
473 
474     state = WRITING_DATA;
475 }
476 
477 void
readGreeting()478 Ftp::Relay::readGreeting()
479 {
480     assert(!master().clientReadGreeting);
481 
482     switch (ctrl.replycode) {
483     case 220:
484         updateMaster().clientReadGreeting = true;
485         if (serverState() == fssBegin)
486             serverState(fssConnected);
487 
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.
491 
492         start();
493         break;
494     case 120:
495         if (NULL != ctrl.message)
496             debugs(9, DBG_IMPORTANT, "FTP server is busy: " << ctrl.message->key);
497         forwardPreliminaryReply(&Ftp::Relay::scheduleReadControlReply);
498         break;
499     default:
500         failed();
501         break;
502     }
503 }
504 
505 void
sendCommand()506 Ftp::Relay::sendCommand()
507 {
508     if (!fwd->request->header.has(Http::HdrType::FTP_COMMAND)) {
509         abortAll("Internal error: FTP relay request with no command");
510         return;
511     }
512 
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 &params = header.findEntry(Http::HdrType::FTP_ARGUMENTS)->value;
518 
519     if (params.size() > 0)
520         debugs(9, 5, "command: " << cmd << ", parameters: " << params);
521     else
522         debugs(9, 5, "command: " << cmd << ", no parameters");
523 
524     if (serverState() == fssHandlePasv ||
525             serverState() == fssHandleEpsv ||
526             serverState() == fssHandleEprt ||
527             serverState() == fssHandlePort) {
528         sendPassive();
529         return;
530     }
531 
532     SBuf buf;
533     if (params.size() > 0)
534         buf.Printf("%s %s%s", cmd.termedBuf(), params.termedBuf(), Ftp::crlf);
535     else
536         buf.Printf("%s%s", cmd.termedBuf(), Ftp::crlf);
537 
538     writeCommand(buf.c_str());
539 
540     state =
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 :
548         SENT_COMMAND;
549 
550     if (state == SENT_DATA_REQUEST) {
551         CbcPointer<ConnStateData> &mgr = fwd->request->clientConnectionManager;
552         if (mgr.valid()) {
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;
559             }
560         }
561     }
562 }
563 
564 void
readReply()565 Ftp::Relay::readReply()
566 {
567     assert(serverState() == fssConnected ||
568            serverState() == fssHandleUploadRequest);
569 
570     if (100 <= ctrl.replycode && ctrl.replycode < 200)
571         forwardPreliminaryReply(&Ftp::Relay::scheduleReadControlReply);
572     else
573         forwardReply();
574 }
575 
576 void
readFeatReply()577 Ftp::Relay::readFeatReply()
578 {
579     assert(serverState() == fssHandleFeat);
580 
581     if (100 <= ctrl.replycode && ctrl.replycode < 200)
582         return; // ignore preliminary replies
583 
584     forwardReply();
585 }
586 
587 void
readPasvReply()588 Ftp::Relay::readPasvReply()
589 {
590     assert(serverState() == fssHandlePasv || serverState() == fssHandleEpsv || serverState() == fssHandlePort || serverState() == fssHandleEprt);
591 
592     if (100 <= ctrl.replycode && ctrl.replycode < 200)
593         return; // ignore preliminary replies
594 
595     if (handlePasvReply(updateMaster().clientDataAddr))
596         forwardReply();
597     else
598         forwardError();
599 }
600 
601 void
readEpsvReply()602 Ftp::Relay::readEpsvReply()
603 {
604     if (100 <= ctrl.replycode && ctrl.replycode < 200)
605         return; // ignore preliminary replies
606 
607     if (handleEpsvReply(updateMaster().clientDataAddr)) {
608         if (ctrl.message == NULL)
609             return; // didn't get complete reply yet
610 
611         forwardReply();
612     } else
613         forwardError();
614 }
615 
616 void
readDataReply()617 Ftp::Relay::readDataReply()
618 {
619     assert(serverState() == fssHandleDataRequest ||
620            serverState() == fssHandleUploadRequest);
621 
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*/)
626             startDataUpload();
627         else // serverState() == fssHandleUploadRequest
628             forwardPreliminaryReply(&Ftp::Relay::startDataUpload);
629     } else
630         forwardReply();
631 }
632 
633 bool
startDirTracking()634 Ftp::Relay::startDirTracking()
635 {
636     if (!fwd->request->clientConnectionManager->port->ftp_track_dirs)
637         return false;
638 
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;
644 
645     ctrl.last_command = NULL;
646     ctrl.last_reply = NULL;
647     ctrl.message = NULL;
648     ctrl.offset = 0;
649     writeCommand("PWD\r\n");
650     return true;
651 }
652 
653 void
stopDirTracking()654 Ftp::Relay::stopDirTracking()
655 {
656     debugs(9, 5, "got code from pwd: " << ctrl.replycode << ", msg: " << ctrl.last_reply);
657 
658     if (ctrl.replycode == 257)
659         updateMaster().workingDir = Ftp::UnescapeDoubleQuoted(ctrl.last_reply);
660 
661     wordlistDestroy(&ctrl.message);
662     safe_free(ctrl.last_command);
663     safe_free(ctrl.last_reply);
664 
665     ctrl.message = savedReply.message;
666     ctrl.last_command = savedReply.lastCommand;
667     ctrl.last_reply = savedReply.lastReply;
668     ctrl.replycode = savedReply.replyCode;
669 
670     savedReply.message = NULL;
671     savedReply.lastReply = NULL;
672     savedReply.lastCommand = NULL;
673 }
674 
675 void
readCwdOrCdupReply()676 Ftp::Relay::readCwdOrCdupReply()
677 {
678     assert(serverState() == fssHandleCwd ||
679            serverState() == fssHandleCdup);
680 
681     debugs(9, 5, "got code " << ctrl.replycode << ", msg: " << ctrl.last_reply);
682 
683     if (100 <= ctrl.replycode && ctrl.replycode < 200)
684         return;
685 
686     if (weAreTrackingDir()) { // we are tracking
687         stopDirTracking(); // and forward the delayed response below
688     } else if (startDirTracking())
689         return;
690 
691     forwardReply();
692 }
693 
694 void
readUserOrPassReply()695 Ftp::Relay::readUserOrPassReply()
696 {
697     if (100 <= ctrl.replycode && ctrl.replycode < 200)
698         return; //Just ignore
699 
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())
704             return;
705     }
706 
707     forwardReply();
708 }
709 
710 void
readTransferDoneReply()711 Ftp::Relay::readTransferDoneReply()
712 {
713     debugs(9, 3, status());
714 
715     if (ctrl.replycode != 226 && ctrl.replycode != 250) {
716         debugs(9, DBG_IMPORTANT, "got FTP code " << ctrl.replycode <<
717                " after reading response data");
718     }
719 
720     debugs(9, 2, "Complete data downloading");
721 
722     serverComplete();
723 }
724 
725 void
dataChannelConnected(const CommConnectCbParams & io)726 Ftp::Relay::dataChannelConnected(const CommConnectCbParams &io)
727 {
728     debugs(9, 3, status());
729     data.opener = NULL;
730 
731     if (io.flag != Comm::OK) {
732         debugs(9, 2, "failed to connect FTP server data channel");
733         forwardError(ERR_CONNECT_FAIL, io.xerrno);
734         return;
735     }
736 
737     debugs(9, 2, "connected FTP server data channel: " << io.conn);
738 
739     data.opened(io.conn, dataCloser());
740 
741     sendCommand();
742 }
743 
744 void
scheduleReadControlReply()745 Ftp::Relay::scheduleReadControlReply()
746 {
747     Ftp::Client::scheduleReadControlReply(0);
748 }
749 
750 bool
abortOnData(const char * reason)751 Ftp::Relay::abortOnData(const char *reason)
752 {
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
757 
758 #if USE_ADAPTATION
759     if (adaptedBodySource != NULL)
760         stopConsumingFrom(adaptedBodySource);
761 #endif
762 
763     if (Comm::IsConnOpen(data.conn))
764         dataComplete();
765 
766     return !Comm::IsConnOpen(ctrl.conn);
767 }
768 
769 void
stopOriginWait(int code)770 Ftp::Relay::stopOriginWait(int code)
771 {
772     if (originWaitInProgress) {
773         CbcPointer<ConnStateData> &mgr = fwd->request->clientConnectionManager;
774         if (mgr.valid()) {
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);
780             }
781         }
782         originWaitInProgress = false;
783     }
784 }
785 
786 void
abort(void * d)787 Ftp::Relay::abort(void *d)
788 {
789     Ftp::Relay *ftpClient = (Ftp::Relay *)d;
790     debugs(9, 2, "Client Data connection closed!");
791     if (!cbdataReferenceValid(ftpClient))
792         return;
793     if (Comm::IsConnOpen(ftpClient->data.conn))
794         ftpClient->dataComplete();
795 }
796 
797 AsyncJob::Pointer
StartRelay(FwdState * const fwdState)798 Ftp::StartRelay(FwdState *const fwdState)
799 {
800     return AsyncJob::Start(new Ftp::Relay(fwdState));
801 }
802 
803