]> git.ipfire.org Git - thirdparty/squid.git/blob - src/clients/FtpRelay.cc
Source Format Enforcement (#532)
[thirdparty/squid.git] / src / clients / FtpRelay.cc
1 /*
2 * Copyright (C) 1996-2020 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();
44 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();
73 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
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
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
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
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
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 &
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 &
244 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
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
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
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
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
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
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
359 Ftp::Relay::handleRequestBodyProducerAborted()
360 {
361 ::Client::handleRequestBodyProducerAborted();
362
363 failed(ERR_READ_ERROR);
364 }
365
366 bool
367 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
374 Ftp::Relay::forwardReply()
375 {
376 assert(entry->isEmpty());
377
378 HttpReply *const reply = createHttpReply(Http::scNoContent);
379 reply->sources |= Http::Message::srcFtp;
380
381 setVirginReply(reply);
382 adaptOrFinalizeReply();
383
384 serverComplete();
385 }
386
387 void
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
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
419 Ftp::Relay::forwardError(err_type error, int xerrno)
420 {
421 failed(error, xerrno);
422 }
423
424 HttpReply *
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
437 Ftp::Relay::handleDataRequest()
438 {
439 data.addr(master().clientDataAddr);
440 connectDataChannel();
441 }
442
443 void
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 |= Http::Message::srcFtp;
453
454 setVirginReply(reply);
455 adaptOrFinalizeReply();
456
457 maybeReadVirginBody();
458 state = READING_DATA;
459 }
460
461 void
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
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
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
565 Ftp::Relay::readReply()
566 {
567 assert(serverState() == fssConnected ||
568 serverState() == fssHandleUploadRequest);
569
570 if (Is1xx(ctrl.replycode))
571 forwardPreliminaryReply(&Ftp::Relay::scheduleReadControlReply);
572 else
573 forwardReply();
574 }
575
576 void
577 Ftp::Relay::readFeatReply()
578 {
579 assert(serverState() == fssHandleFeat);
580
581 if (Is1xx(ctrl.replycode))
582 return; // ignore preliminary replies
583
584 forwardReply();
585 }
586
587 void
588 Ftp::Relay::readPasvReply()
589 {
590 assert(serverState() == fssHandlePasv || serverState() == fssHandleEpsv || serverState() == fssHandlePort || serverState() == fssHandleEprt);
591
592 if (Is1xx(ctrl.replycode))
593 return; // ignore preliminary replies
594
595 if (handlePasvReply(updateMaster().clientDataAddr))
596 forwardReply();
597 else
598 forwardError();
599 }
600
601 void
602 Ftp::Relay::readEpsvReply()
603 {
604 if (Is1xx(ctrl.replycode))
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
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
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
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
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 (Is1xx(ctrl.replycode))
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
695 Ftp::Relay::readUserOrPassReply()
696 {
697 if (Is1xx(ctrl.replycode))
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
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
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
745 Ftp::Relay::scheduleReadControlReply()
746 {
747 Ftp::Client::scheduleReadControlReply(0);
748 }
749
750 bool
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
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
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
798 Ftp::StartRelay(FwdState *const fwdState)
799 {
800 return AsyncJob::Start(new Ftp::Relay(fwdState));
801 }
802