]> git.ipfire.org Git - thirdparty/squid.git/blob - src/clients/FtpRelay.cc
Changes in response to Amos' squid-dev review dated 2014/08/08.
[thirdparty/squid.git] / src / clients / FtpRelay.cc
1 /*
2 * DEBUG: section 09 File Transfer Protocol (FTP)
3 *
4 */
5
6 #include "squid.h"
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"
15 #include "SBuf.h"
16 #include "Server.h"
17 #include "servers/FtpServer.h"
18 #include "SquidTime.h"
19 #include "Store.h"
20 #include "wordlist.h"
21
22 namespace Ftp
23 {
24
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
29 {
30 public:
31 explicit Relay(FwdState *const fwdState);
32 virtual ~Relay();
33
34 protected:
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);
39
40 /* Ftp::Client API */
41 virtual void failed(err_type error = ERR_NONE, int xerrno = 0);
42 virtual void dataChannelConnected(const CommConnectCbParams &io);
43
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();
51
52 /* AsyncJob API */
53 virtual void start();
54
55 void forwardReply();
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;}
65
66 typedef void (Relay::*PreliminaryCb)();
67 void forwardPreliminaryReply(const PreliminaryCb cb);
68 void proceedAfterPreliminaryReply();
69 PreliminaryCb thePreliminaryCb;
70
71 typedef void (Relay::*SM_FUNC)();
72 static const SM_FUNC SM_FUNCS[];
73 void readGreeting();
74 void sendCommand();
75 void readReply();
76 void readFeatReply();
77 void readPasvReply();
78 void readDataReply();
79 void readTransferDoneReply();
80 void readEpsvReply();
81 void readCwdOrCdupReply();
82 void readUserOrPassReply();
83
84 void scheduleReadControlReply();
85
86 bool forwardingCompleted; ///< completeForwarding() has been called
87
88 struct {
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
94
95 CBDATA_CLASS2(Relay);
96 };
97
98 } // namespace Ftp
99
100 CBDATA_NAMESPACED_CLASS_INIT(Ftp, Relay);
101
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
109 NULL, // SENT_EPRT
110 NULL, // SENT_PORT
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
130 NULL
131 };
132
133 Ftp::Relay::Relay(FwdState *const fwdState):
134 AsyncJob("Ftp::Relay"),
135 Ftp::Client(fwdState),
136 forwardingCompleted(false)
137 {
138 savedReply.message = NULL;
139 savedReply.lastCommand = NULL;
140 savedReply.lastReply = NULL;
141 savedReply.replyCode = 0;
142
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();
146 }
147
148 Ftp::Relay::~Relay()
149 {
150 closeServer(); // TODO: move to Server.cc?
151 if (savedReply.message)
152 wordlistDestroy(&savedReply.message);
153
154 xfree(savedReply.lastCommand);
155 xfree(savedReply.lastReply);
156 }
157
158 void
159 Ftp::Relay::start()
160 {
161 if (!master().clientReadGreeting)
162 Ftp::Client::start();
163 else if (serverState() == fssHandleDataRequest ||
164 serverState() == fssHandleUploadRequest)
165 handleDataRequest();
166 else
167 sendCommand();
168 }
169
170 /// Keep control connection for future requests, after we are done with it.
171 /// Similar to COMPLETE_PERSISTENT_MSG handling in http.cc.
172 void
173 Ftp::Relay::serverComplete()
174 {
175 CbcPointer<ConnStateData> &mgr = fwd->request->clientConnectionManager;
176 if (mgr.valid()) {
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);
183 ctrl.close();
184 } else {
185 mgr->pinConnection(ctrl.conn, fwd->request,
186 ctrl.conn->getPeer(),
187 fwd->request->flags.connectionAuth);
188 ctrl.forget();
189 }
190 }
191 }
192 Ftp::Client::serverComplete();
193 }
194
195 /// Safely returns the master state,
196 /// with safety checks in case the Ftp::Server side of the master xact is gone.
197 Ftp::MasterState &
198 Ftp::Relay::updateMaster()
199 {
200 CbcPointer<ConnStateData> &mgr = fwd->request->clientConnectionManager;
201 if (mgr.valid()) {
202 if (Ftp::Server *srv = dynamic_cast<Ftp::Server*>(mgr.get()))
203 return srv->master;
204 }
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();
209 return Master;
210 }
211
212 /// A const variant of updateMaster().
213 const Ftp::MasterState &
214 Ftp::Relay::master() const
215 {
216 return const_cast<Ftp::Relay*>(this)->updateMaster(); // avoid code dupe
217 }
218
219 /// Changes server state and debugs about that important event.
220 void
221 Ftp::Relay::serverState(const Ftp::ServerState newState)
222 {
223 Ftp::ServerState &cltState = updateMaster().serverState;
224 debugs(9, 3, "client state was " << cltState << " now: " << newState);
225 cltState = newState;
226 }
227
228 /**
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).
233 *
234 \todo Rewrite FwdState to ignore double completion?
235 */
236 void
237 Ftp::Relay::completeForwarding()
238 {
239 debugs(9, 5, forwardingCompleted);
240 if (forwardingCompleted)
241 return;
242 forwardingCompleted = true;
243 Ftp::Client::completeForwarding();
244 }
245
246 void
247 Ftp::Relay::failed(err_type error, int xerrno)
248 {
249 if (!doneWithServer())
250 serverState(fssError);
251
252 // TODO: we need to customize ErrorState instead
253 if (entry->isEmpty())
254 failedErrorMessage(error, xerrno); // as a reply
255
256 Ftp::Client::failed(error, xerrno);
257 }
258
259 void
260 Ftp::Relay::failedErrorMessage(err_type error, int xerrno)
261 {
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);
267 }
268
269 void
270 Ftp::Relay::processReplyBody()
271 {
272 debugs(9, 3, status());
273
274 if (EBIT_TEST(entry->flags, ENTRY_ABORTED)) {
275 /*
276 * probably was aborted because content length exceeds one
277 * of the maximum size limits.
278 */
279 abortTransaction("entry aborted after calling appendSuccessHeader()");
280 return;
281 }
282
283 #if USE_ADAPTATION
284
285 if (adaptationAccessCheckPending) {
286 debugs(9, 3, "returning due to adaptationAccessCheckPending");
287 return;
288 }
289
290 #endif
291
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);
297 }
298
299 entry->flush();
300
301 maybeReadVirginBody();
302 }
303
304 void
305 Ftp::Relay::handleControlReply()
306 {
307 if (!request->clientConnectionManager.valid()) {
308 debugs(9, 5, "client connection gone");
309 closeServer();
310 return;
311 }
312
313 Ftp::Client::handleControlReply();
314 if (ctrl.message == NULL)
315 return; // didn't get complete reply yet
316
317 assert(state < END);
318 assert(this->SM_FUNCS[state] != NULL);
319 (this->*SM_FUNCS[state])();
320 }
321
322 void
323 Ftp::Relay::handleRequestBodyProducerAborted()
324 {
325 ::ServerStateData::handleRequestBodyProducerAborted();
326
327 failed(ERR_READ_ERROR);
328 }
329
330 bool
331 Ftp::Relay::mayReadVirginReplyBody() const
332 {
333 // TODO: move this method to the regular FTP server?
334 return Comm::IsConnOpen(data.conn);
335 }
336
337 void
338 Ftp::Relay::forwardReply()
339 {
340 assert(entry->isEmpty());
341 EBIT_CLR(entry->flags, ENTRY_FWD_HDR_WAIT);
342
343 HttpReply *const reply = createHttpReply(Http::scNoContent);
344
345 setVirginReply(reply);
346 adaptOrFinalizeReply();
347
348 serverComplete();
349 }
350
351 void
352 Ftp::Relay::forwardPreliminaryReply(const PreliminaryCb cb)
353 {
354 debugs(9, 5, "forwarding preliminary reply to client");
355
356 // we must prevent concurrent ConnStateData::sendControlMsg() calls
357 Must(thePreliminaryCb == NULL);
358 thePreliminaryCb = cb;
359
360 const HttpReply::Pointer reply = createHttpReply(Http::scContinue);
361
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);
366
367 CallJobHere1(9, 4, request->clientConnectionManager, ConnStateData,
368 ConnStateData::sendControlMsg, HttpControlMsg(reply, call));
369 }
370
371 void
372 Ftp::Relay::proceedAfterPreliminaryReply()
373 {
374 debugs(9, 5, "proceeding after preliminary reply to client");
375
376 Must(thePreliminaryCb != NULL);
377 const PreliminaryCb cb = thePreliminaryCb;
378 thePreliminaryCb = NULL;
379 (this->*cb)();
380 }
381
382 void
383 Ftp::Relay::forwardError(err_type error, int xerrno)
384 {
385 failed(error, xerrno);
386 }
387
388 HttpReply *
389 Ftp::Relay::createHttpReply(const Http::StatusCode httpStatus, const int64_t clen)
390 {
391 HttpReply *const reply = Ftp::HttpReplyWrapper(ctrl.replycode, ctrl.last_reply, httpStatus, clen);
392 if (ctrl.message) {
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
396 }
397 return reply;
398 }
399
400 void
401 Ftp::Relay::handleDataRequest()
402 {
403 data.addr(master().clientDataAddr);
404 connectDataChannel();
405 }
406
407 void
408 Ftp::Relay::startDataDownload()
409 {
410 assert(Comm::IsConnOpen(data.conn));
411
412 debugs(9, 3, "begin data transfer from " << data.conn->remote <<
413 " (" << data.conn->local << ")");
414
415 HttpReply *const reply = createHttpReply(Http::scOkay, -1);
416 EBIT_CLR(entry->flags, ENTRY_FWD_HDR_WAIT);
417 setVirginReply(reply);
418 adaptOrFinalizeReply();
419
420 maybeReadVirginBody();
421 state = READING_DATA;
422 }
423
424 void
425 Ftp::Relay::startDataUpload()
426 {
427 assert(Comm::IsConnOpen(data.conn));
428
429 debugs(9, 3, "begin data transfer to " << data.conn->remote <<
430 " (" << data.conn->local << ")");
431
432 if (!startRequestBodyFlow()) { // register to receive body data
433 failed();
434 return;
435 }
436
437 state = WRITING_DATA;
438 }
439
440 void
441 Ftp::Relay::readGreeting()
442 {
443 assert(!master().clientReadGreeting);
444
445 switch (ctrl.replycode) {
446 case 220:
447 updateMaster().clientReadGreeting = true;
448 if (serverState() == fssBegin)
449 serverState(fssConnected);
450
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.
454
455 start();
456 break;
457 case 120:
458 if (NULL != ctrl.message)
459 debugs(9, DBG_IMPORTANT, "FTP server is busy: " << ctrl.message->key);
460 forwardPreliminaryReply(&Ftp::Relay::scheduleReadControlReply);
461 break;
462 default:
463 failed();
464 break;
465 }
466 }
467
468 void
469 Ftp::Relay::sendCommand()
470 {
471 if (!fwd->request->header.has(HDR_FTP_COMMAND)) {
472 abortTransaction("Internal error: FTP relay request with no command");
473 return;
474 }
475
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 &params = header.findEntry(HDR_FTP_ARGUMENTS)->value;
481
482 if (params.size() > 0)
483 debugs(9, 5, "command: " << cmd << ", parameters: " << params);
484 else
485 debugs(9, 5, "command: " << cmd << ", no parameters");
486
487 if (serverState() == fssHandlePasv ||
488 serverState() == fssHandleEpsv ||
489 serverState() == fssHandleEprt ||
490 serverState() == fssHandlePort) {
491 sendPassive();
492 return;
493 }
494
495 SBuf buf;
496 if (params.size() > 0)
497 buf.Printf("%s %s%s", cmd.termedBuf(), params.termedBuf(), Ftp::crlf);
498 else
499 buf.Printf("%s%s", cmd.termedBuf(), Ftp::crlf);
500
501 writeCommand(buf.c_str());
502
503 state =
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 :
511 SENT_COMMAND;
512 }
513
514 void
515 Ftp::Relay::readReply()
516 {
517 assert(serverState() == fssConnected ||
518 serverState() == fssHandleUploadRequest);
519
520 if (100 <= ctrl.replycode && ctrl.replycode < 200)
521 forwardPreliminaryReply(&Ftp::Relay::scheduleReadControlReply);
522 else
523 forwardReply();
524 }
525
526 void
527 Ftp::Relay::readFeatReply()
528 {
529 assert(serverState() == fssHandleFeat);
530
531 if (100 <= ctrl.replycode && ctrl.replycode < 200)
532 return; // ignore preliminary replies
533
534 forwardReply();
535 }
536
537 void
538 Ftp::Relay::readPasvReply()
539 {
540 assert(serverState() == fssHandlePasv || serverState() == fssHandleEpsv || serverState() == fssHandlePort || serverState() == fssHandleEprt);
541
542 if (100 <= ctrl.replycode && ctrl.replycode < 200)
543 return; // ignore preliminary replies
544
545 if (handlePasvReply(updateMaster().clientDataAddr))
546 forwardReply();
547 else
548 forwardError();
549 }
550
551 void
552 Ftp::Relay::readEpsvReply()
553 {
554 if (100 <= ctrl.replycode && ctrl.replycode < 200)
555 return; // ignore preliminary replies
556
557 if (handleEpsvReply(updateMaster().clientDataAddr)) {
558 if (ctrl.message == NULL)
559 return; // didn't get complete reply yet
560
561 forwardReply();
562 } else
563 forwardError();
564 }
565
566 void
567 Ftp::Relay::readDataReply()
568 {
569 assert(serverState() == fssHandleDataRequest ||
570 serverState() == fssHandleUploadRequest);
571
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);
577 } else
578 forwardReply();
579 }
580
581 bool
582 Ftp::Relay::startDirTracking()
583 {
584 if (!fwd->request->clientConnectionManager->port->ftp_track_dirs)
585 return false;
586
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;
592
593 ctrl.last_command = NULL;
594 ctrl.last_reply = NULL;
595 ctrl.message = NULL;
596 ctrl.offset = 0;
597 writeCommand("PWD\r\n");
598 return true;
599 }
600
601 void
602 Ftp::Relay::stopDirTracking()
603 {
604 debugs(9, 5, "got code from pwd: " << ctrl.replycode << ", msg: " << ctrl.last_reply);
605
606 if (ctrl.replycode == 257)
607 updateMaster().workingDir = Ftp::UnescapeDoubleQuoted(ctrl.last_reply);
608
609 wordlistDestroy(&ctrl.message);
610 safe_free(ctrl.last_command);
611 safe_free(ctrl.last_reply);
612
613 ctrl.message = savedReply.message;
614 ctrl.last_command = savedReply.lastCommand;
615 ctrl.last_reply = savedReply.lastReply;
616 ctrl.replycode = savedReply.replyCode;
617
618 savedReply.message = NULL;
619 savedReply.lastReply = NULL;
620 savedReply.lastCommand = NULL;
621 }
622
623 void
624 Ftp::Relay::readCwdOrCdupReply()
625 {
626 assert(serverState() == fssHandleCwd ||
627 serverState() == fssHandleCdup);
628
629 debugs(9, 5, "got code " << ctrl.replycode << ", msg: " << ctrl.last_reply);
630
631 if (100 <= ctrl.replycode && ctrl.replycode < 200)
632 return;
633
634 if (weAreTrackingDir()) { // we are tracking
635 stopDirTracking(); // and forward the delayed response below
636 } else if (startDirTracking())
637 return;
638
639 forwardReply();
640 }
641
642 void
643 Ftp::Relay::readUserOrPassReply()
644 {
645 if (100 <= ctrl.replycode && ctrl.replycode < 200)
646 return; //Just ignore
647
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())
652 return;
653 }
654
655 forwardReply();
656 }
657
658 void
659 Ftp::Relay::readTransferDoneReply()
660 {
661 debugs(9, 3, status());
662
663 if (ctrl.replycode != 226 && ctrl.replycode != 250) {
664 debugs(9, DBG_IMPORTANT, "got FTP code " << ctrl.replycode <<
665 " after reading response data");
666 }
667
668 serverComplete();
669 }
670
671 void
672 Ftp::Relay::dataChannelConnected(const CommConnectCbParams &io)
673 {
674 debugs(9, 3, status());
675 data.opener = NULL;
676
677 if (io.flag != Comm::OK) {
678 debugs(9, 2, "failed to connect FTP server data channel");
679 forwardError(ERR_CONNECT_FAIL, io.xerrno);
680 return;
681 }
682
683 debugs(9, 2, "connected FTP server data channel: " << io.conn);
684
685 data.opened(io.conn, dataCloser());
686
687 sendCommand();
688 }
689
690 void
691 Ftp::Relay::scheduleReadControlReply()
692 {
693 Ftp::Client::scheduleReadControlReply(0);
694 }
695
696 AsyncJob::Pointer
697 Ftp::StartRelay(FwdState *const fwdState)
698 {
699 return AsyncJob::Start(new Ftp::Relay(fwdState));
700 }