]> git.ipfire.org Git - thirdparty/squid.git/blob - src/clients/FtpRelay.cc
SourceFormat Enforcement
[thirdparty/squid.git] / src / clients / FtpRelay.cc
1 /*
2 * Copyright (C) 1996-2015 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 "HttpHdrCc.h"
19 #include "HttpRequest.h"
20 #include "SBuf.h"
21 #include "servers/FtpServer.h"
22 #include "SquidTime.h"
23 #include "Store.h"
24 #include "wordlist.h"
25
26 namespace Ftp
27 {
28
29 /// An FTP client receiving native FTP commands from our FTP server
30 /// (Ftp::Server), forwarding them to the next FTP hop,
31 /// and then relaying FTP replies back to our FTP server.
32 class Relay: public Ftp::Client
33 {
34 CBDATA_CLASS(Relay);
35
36 public:
37 explicit Relay(FwdState *const fwdState);
38 virtual ~Relay();
39
40 protected:
41 const Ftp::MasterState &master() const;
42 Ftp::MasterState &updateMaster();
43 Ftp::ServerState serverState() const { return master().serverState; }
44 void serverState(const Ftp::ServerState newState);
45
46 /* Ftp::Client API */
47 virtual void failed(err_type error = ERR_NONE, int xerrno = 0);
48 virtual void dataChannelConnected(const CommConnectCbParams &io);
49
50 /* Client API */
51 virtual void serverComplete();
52 virtual void handleControlReply();
53 virtual void processReplyBody();
54 virtual void handleRequestBodyProducerAborted();
55 virtual bool mayReadVirginReplyBody() const;
56 virtual void completeForwarding();
57
58 /* AsyncJob API */
59 virtual void start();
60
61 void forwardReply();
62 void forwardError(err_type error = ERR_NONE, int xerrno = 0);
63 void failedErrorMessage(err_type error, int xerrno);
64 HttpReply *createHttpReply(const Http::StatusCode httpStatus, const int64_t clen = 0);
65 void handleDataRequest();
66 void startDataDownload();
67 void startDataUpload();
68 bool startDirTracking();
69 void stopDirTracking();
70 bool weAreTrackingDir() const {return savedReply.message != NULL;}
71
72 typedef void (Relay::*PreliminaryCb)();
73 void forwardPreliminaryReply(const PreliminaryCb cb);
74 void proceedAfterPreliminaryReply();
75 PreliminaryCb thePreliminaryCb;
76
77 typedef void (Relay::*SM_FUNC)();
78 static const SM_FUNC SM_FUNCS[];
79 void readGreeting();
80 void sendCommand();
81 void readReply();
82 void readFeatReply();
83 void readPasvReply();
84 void readDataReply();
85 void readTransferDoneReply();
86 void readEpsvReply();
87 void readCwdOrCdupReply();
88 void readUserOrPassReply();
89
90 void scheduleReadControlReply();
91
92 bool forwardingCompleted; ///< completeForwarding() has been called
93
94 struct {
95 wordlist *message; ///< reply message, one wordlist entry per message line
96 char *lastCommand; ///< the command caused the reply
97 char *lastReply; ///< last line of reply: reply status plus message
98 int replyCode; ///< the reply status
99 } savedReply; ///< set and delayed while we are tracking using PWD
100 };
101
102 } // namespace Ftp
103
104 CBDATA_NAMESPACED_CLASS_INIT(Ftp, Relay);
105
106 const Ftp::Relay::SM_FUNC Ftp::Relay::SM_FUNCS[] = {
107 &Ftp::Relay::readGreeting, // BEGIN
108 &Ftp::Relay::readUserOrPassReply, // SENT_USER
109 &Ftp::Relay::readUserOrPassReply, // SENT_PASS
110 NULL,/* &Ftp::Relay::readReply */ // SENT_TYPE
111 NULL,/* &Ftp::Relay::readReply */ // SENT_MDTM
112 NULL,/* &Ftp::Relay::readReply */ // SENT_SIZE
113 NULL, // SENT_EPRT
114 NULL, // SENT_PORT
115 &Ftp::Relay::readEpsvReply, // SENT_EPSV_ALL
116 &Ftp::Relay::readEpsvReply, // SENT_EPSV_1
117 &Ftp::Relay::readEpsvReply, // SENT_EPSV_2
118 &Ftp::Relay::readPasvReply, // SENT_PASV
119 &Ftp::Relay::readCwdOrCdupReply, // SENT_CWD
120 NULL,/* &Ftp::Relay::readDataReply, */ // SENT_LIST
121 NULL,/* &Ftp::Relay::readDataReply, */ // SENT_NLST
122 NULL,/* &Ftp::Relay::readReply */ // SENT_REST
123 NULL,/* &Ftp::Relay::readDataReply */ // SENT_RETR
124 NULL,/* &Ftp::Relay::readReply */ // SENT_STOR
125 NULL,/* &Ftp::Relay::readReply */ // SENT_QUIT
126 &Ftp::Relay::readTransferDoneReply, // READING_DATA
127 &Ftp::Relay::readReply, // WRITING_DATA
128 NULL,/* &Ftp::Relay::readReply */ // SENT_MKDIR
129 &Ftp::Relay::readFeatReply, // SENT_FEAT
130 NULL,/* &Ftp::Relay::readPwdReply */ // SENT_PWD
131 &Ftp::Relay::readCwdOrCdupReply, // SENT_CDUP
132 &Ftp::Relay::readDataReply,// SENT_DATA_REQUEST
133 &Ftp::Relay::readReply, // SENT_COMMAND
134 NULL
135 };
136
137 Ftp::Relay::Relay(FwdState *const fwdState):
138 AsyncJob("Ftp::Relay"),
139 Ftp::Client(fwdState),
140 thePreliminaryCb(NULL),
141 forwardingCompleted(false)
142 {
143 savedReply.message = NULL;
144 savedReply.lastCommand = NULL;
145 savedReply.lastReply = NULL;
146 savedReply.replyCode = 0;
147
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();
151 }
152
153 Ftp::Relay::~Relay()
154 {
155 closeServer(); // TODO: move to clients/Client.cc?
156 if (savedReply.message)
157 wordlistDestroy(&savedReply.message);
158
159 xfree(savedReply.lastCommand);
160 xfree(savedReply.lastReply);
161 }
162
163 void
164 Ftp::Relay::start()
165 {
166 if (!master().clientReadGreeting)
167 Ftp::Client::start();
168 else if (serverState() == fssHandleDataRequest ||
169 serverState() == fssHandleUploadRequest)
170 handleDataRequest();
171 else
172 sendCommand();
173 }
174
175 /// Keep control connection for future requests, after we are done with it.
176 /// Similar to COMPLETE_PERSISTENT_MSG handling in http.cc.
177 void
178 Ftp::Relay::serverComplete()
179 {
180 CbcPointer<ConnStateData> &mgr = fwd->request->clientConnectionManager;
181 if (mgr.valid()) {
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);
188 ctrl.close();
189 } else {
190 mgr->pinConnection(ctrl.conn, fwd->request,
191 ctrl.conn->getPeer(),
192 fwd->request->flags.connectionAuth);
193 ctrl.forget();
194 }
195 }
196 }
197 Ftp::Client::serverComplete();
198 }
199
200 /// Safely returns the master state,
201 /// with safety checks in case the Ftp::Server side of the master xact is gone.
202 Ftp::MasterState &
203 Ftp::Relay::updateMaster()
204 {
205 CbcPointer<ConnStateData> &mgr = fwd->request->clientConnectionManager;
206 if (mgr.valid()) {
207 if (Ftp::Server *srv = dynamic_cast<Ftp::Server*>(mgr.get()))
208 return *srv->master;
209 }
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();
214 return Master;
215 }
216
217 /// A const variant of updateMaster().
218 const Ftp::MasterState &
219 Ftp::Relay::master() const
220 {
221 return const_cast<Ftp::Relay*>(this)->updateMaster(); // avoid code dupe
222 }
223
224 /// Changes server state and debugs about that important event.
225 void
226 Ftp::Relay::serverState(const Ftp::ServerState newState)
227 {
228 Ftp::ServerState &cltState = updateMaster().serverState;
229 debugs(9, 3, "client state was " << cltState << " now: " << newState);
230 cltState = newState;
231 }
232
233 /**
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).
238 *
239 \todo Rewrite FwdState to ignore double completion?
240 */
241 void
242 Ftp::Relay::completeForwarding()
243 {
244 debugs(9, 5, forwardingCompleted);
245 if (forwardingCompleted)
246 return;
247 forwardingCompleted = true;
248 Ftp::Client::completeForwarding();
249 }
250
251 void
252 Ftp::Relay::failed(err_type error, int xerrno)
253 {
254 if (!doneWithServer())
255 serverState(fssError);
256
257 // TODO: we need to customize ErrorState instead
258 if (entry->isEmpty())
259 failedErrorMessage(error, xerrno); // as a reply
260
261 Ftp::Client::failed(error, xerrno);
262 }
263
264 void
265 Ftp::Relay::failedErrorMessage(err_type error, int xerrno)
266 {
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);
272 }
273
274 void
275 Ftp::Relay::processReplyBody()
276 {
277 debugs(9, 3, status());
278
279 if (EBIT_TEST(entry->flags, ENTRY_ABORTED)) {
280 /*
281 * probably was aborted because content length exceeds one
282 * of the maximum size limits.
283 */
284 abortTransaction("entry aborted after calling appendSuccessHeader()");
285 return;
286 }
287
288 #if USE_ADAPTATION
289
290 if (adaptationAccessCheckPending) {
291 debugs(9, 3, "returning due to adaptationAccessCheckPending");
292 return;
293 }
294
295 #endif
296
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);
302 }
303
304 entry->flush();
305
306 maybeReadVirginBody();
307 }
308
309 void
310 Ftp::Relay::handleControlReply()
311 {
312 if (!request->clientConnectionManager.valid()) {
313 debugs(9, 5, "client connection gone");
314 closeServer();
315 return;
316 }
317
318 Ftp::Client::handleControlReply();
319 if (ctrl.message == NULL)
320 return; // didn't get complete reply yet
321
322 assert(state < END);
323 assert(this->SM_FUNCS[state] != NULL);
324 (this->*SM_FUNCS[state])();
325 }
326
327 void
328 Ftp::Relay::handleRequestBodyProducerAborted()
329 {
330 ::Client::handleRequestBodyProducerAborted();
331
332 failed(ERR_READ_ERROR);
333 }
334
335 bool
336 Ftp::Relay::mayReadVirginReplyBody() const
337 {
338 // TODO: move this method to the regular FTP server?
339 return Comm::IsConnOpen(data.conn);
340 }
341
342 void
343 Ftp::Relay::forwardReply()
344 {
345 assert(entry->isEmpty());
346 EBIT_CLR(entry->flags, ENTRY_FWD_HDR_WAIT);
347
348 HttpReply *const reply = createHttpReply(Http::scNoContent);
349
350 setVirginReply(reply);
351 adaptOrFinalizeReply();
352
353 serverComplete();
354 }
355
356 void
357 Ftp::Relay::forwardPreliminaryReply(const PreliminaryCb cb)
358 {
359 debugs(9, 5, "forwarding preliminary reply to client");
360
361 // we must prevent concurrent ConnStateData::sendControlMsg() calls
362 Must(thePreliminaryCb == NULL);
363 thePreliminaryCb = cb;
364
365 const HttpReply::Pointer reply = createHttpReply(Http::scContinue);
366
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);
371
372 CallJobHere1(9, 4, request->clientConnectionManager, ConnStateData,
373 ConnStateData::sendControlMsg, HttpControlMsg(reply, call));
374 }
375
376 void
377 Ftp::Relay::proceedAfterPreliminaryReply()
378 {
379 debugs(9, 5, "proceeding after preliminary reply to client");
380
381 Must(thePreliminaryCb != NULL);
382 const PreliminaryCb cb = thePreliminaryCb;
383 thePreliminaryCb = NULL;
384 (this->*cb)();
385 }
386
387 void
388 Ftp::Relay::forwardError(err_type error, int xerrno)
389 {
390 failed(error, xerrno);
391 }
392
393 HttpReply *
394 Ftp::Relay::createHttpReply(const Http::StatusCode httpStatus, const int64_t clen)
395 {
396 HttpReply *const reply = Ftp::HttpReplyWrapper(ctrl.replycode, ctrl.last_reply, httpStatus, clen);
397 if (ctrl.message) {
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
401 }
402 return reply;
403 }
404
405 void
406 Ftp::Relay::handleDataRequest()
407 {
408 data.addr(master().clientDataAddr);
409 connectDataChannel();
410 }
411
412 void
413 Ftp::Relay::startDataDownload()
414 {
415 assert(Comm::IsConnOpen(data.conn));
416
417 debugs(9, 3, "begin data transfer from " << data.conn->remote <<
418 " (" << data.conn->local << ")");
419
420 HttpReply *const reply = createHttpReply(Http::scOkay, -1);
421 EBIT_CLR(entry->flags, ENTRY_FWD_HDR_WAIT);
422 setVirginReply(reply);
423 adaptOrFinalizeReply();
424
425 maybeReadVirginBody();
426 state = READING_DATA;
427 }
428
429 void
430 Ftp::Relay::startDataUpload()
431 {
432 assert(Comm::IsConnOpen(data.conn));
433
434 debugs(9, 3, "begin data transfer to " << data.conn->remote <<
435 " (" << data.conn->local << ")");
436
437 if (!startRequestBodyFlow()) { // register to receive body data
438 failed();
439 return;
440 }
441
442 state = WRITING_DATA;
443 }
444
445 void
446 Ftp::Relay::readGreeting()
447 {
448 assert(!master().clientReadGreeting);
449
450 switch (ctrl.replycode) {
451 case 220:
452 updateMaster().clientReadGreeting = true;
453 if (serverState() == fssBegin)
454 serverState(fssConnected);
455
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.
459
460 start();
461 break;
462 case 120:
463 if (NULL != ctrl.message)
464 debugs(9, DBG_IMPORTANT, "FTP server is busy: " << ctrl.message->key);
465 forwardPreliminaryReply(&Ftp::Relay::scheduleReadControlReply);
466 break;
467 default:
468 failed();
469 break;
470 }
471 }
472
473 void
474 Ftp::Relay::sendCommand()
475 {
476 if (!fwd->request->header.has(HDR_FTP_COMMAND)) {
477 abortTransaction("Internal error: FTP relay request with no command");
478 return;
479 }
480
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 &params = header.findEntry(HDR_FTP_ARGUMENTS)->value;
486
487 if (params.size() > 0)
488 debugs(9, 5, "command: " << cmd << ", parameters: " << params);
489 else
490 debugs(9, 5, "command: " << cmd << ", no parameters");
491
492 if (serverState() == fssHandlePasv ||
493 serverState() == fssHandleEpsv ||
494 serverState() == fssHandleEprt ||
495 serverState() == fssHandlePort) {
496 sendPassive();
497 return;
498 }
499
500 SBuf buf;
501 if (params.size() > 0)
502 buf.Printf("%s %s%s", cmd.termedBuf(), params.termedBuf(), Ftp::crlf);
503 else
504 buf.Printf("%s%s", cmd.termedBuf(), Ftp::crlf);
505
506 writeCommand(buf.c_str());
507
508 state =
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 :
516 SENT_COMMAND;
517 }
518
519 void
520 Ftp::Relay::readReply()
521 {
522 assert(serverState() == fssConnected ||
523 serverState() == fssHandleUploadRequest);
524
525 if (100 <= ctrl.replycode && ctrl.replycode < 200)
526 forwardPreliminaryReply(&Ftp::Relay::scheduleReadControlReply);
527 else
528 forwardReply();
529 }
530
531 void
532 Ftp::Relay::readFeatReply()
533 {
534 assert(serverState() == fssHandleFeat);
535
536 if (100 <= ctrl.replycode && ctrl.replycode < 200)
537 return; // ignore preliminary replies
538
539 forwardReply();
540 }
541
542 void
543 Ftp::Relay::readPasvReply()
544 {
545 assert(serverState() == fssHandlePasv || serverState() == fssHandleEpsv || serverState() == fssHandlePort || serverState() == fssHandleEprt);
546
547 if (100 <= ctrl.replycode && ctrl.replycode < 200)
548 return; // ignore preliminary replies
549
550 if (handlePasvReply(updateMaster().clientDataAddr))
551 forwardReply();
552 else
553 forwardError();
554 }
555
556 void
557 Ftp::Relay::readEpsvReply()
558 {
559 if (100 <= ctrl.replycode && ctrl.replycode < 200)
560 return; // ignore preliminary replies
561
562 if (handleEpsvReply(updateMaster().clientDataAddr)) {
563 if (ctrl.message == NULL)
564 return; // didn't get complete reply yet
565
566 forwardReply();
567 } else
568 forwardError();
569 }
570
571 void
572 Ftp::Relay::readDataReply()
573 {
574 assert(serverState() == fssHandleDataRequest ||
575 serverState() == fssHandleUploadRequest);
576
577 if (ctrl.replycode == 125 || ctrl.replycode == 150) {
578 if (serverState() == fssHandleDataRequest)
579 forwardPreliminaryReply(&Ftp::Relay::startDataDownload);
580 else if (fwd->request->forcedBodyContinuation /*&& serverState() == fssHandleUploadRequest*/)
581 startDataUpload();
582 else // serverState() == fssHandleUploadRequest
583 forwardPreliminaryReply(&Ftp::Relay::startDataUpload);
584 } else
585 forwardReply();
586 }
587
588 bool
589 Ftp::Relay::startDirTracking()
590 {
591 if (!fwd->request->clientConnectionManager->port->ftp_track_dirs)
592 return false;
593
594 debugs(9, 5, "start directory tracking");
595 savedReply.message = ctrl.message;
596 savedReply.lastCommand = ctrl.last_command;
597 savedReply.lastReply = ctrl.last_reply;
598 savedReply.replyCode = ctrl.replycode;
599
600 ctrl.last_command = NULL;
601 ctrl.last_reply = NULL;
602 ctrl.message = NULL;
603 ctrl.offset = 0;
604 writeCommand("PWD\r\n");
605 return true;
606 }
607
608 void
609 Ftp::Relay::stopDirTracking()
610 {
611 debugs(9, 5, "got code from pwd: " << ctrl.replycode << ", msg: " << ctrl.last_reply);
612
613 if (ctrl.replycode == 257)
614 updateMaster().workingDir = Ftp::UnescapeDoubleQuoted(ctrl.last_reply);
615
616 wordlistDestroy(&ctrl.message);
617 safe_free(ctrl.last_command);
618 safe_free(ctrl.last_reply);
619
620 ctrl.message = savedReply.message;
621 ctrl.last_command = savedReply.lastCommand;
622 ctrl.last_reply = savedReply.lastReply;
623 ctrl.replycode = savedReply.replyCode;
624
625 savedReply.message = NULL;
626 savedReply.lastReply = NULL;
627 savedReply.lastCommand = NULL;
628 }
629
630 void
631 Ftp::Relay::readCwdOrCdupReply()
632 {
633 assert(serverState() == fssHandleCwd ||
634 serverState() == fssHandleCdup);
635
636 debugs(9, 5, "got code " << ctrl.replycode << ", msg: " << ctrl.last_reply);
637
638 if (100 <= ctrl.replycode && ctrl.replycode < 200)
639 return;
640
641 if (weAreTrackingDir()) { // we are tracking
642 stopDirTracking(); // and forward the delayed response below
643 } else if (startDirTracking())
644 return;
645
646 forwardReply();
647 }
648
649 void
650 Ftp::Relay::readUserOrPassReply()
651 {
652 if (100 <= ctrl.replycode && ctrl.replycode < 200)
653 return; //Just ignore
654
655 if (weAreTrackingDir()) { // we are tracking
656 stopDirTracking(); // and forward the delayed response below
657 } else if (ctrl.replycode == 230) { // successful login
658 if (startDirTracking())
659 return;
660 }
661
662 forwardReply();
663 }
664
665 void
666 Ftp::Relay::readTransferDoneReply()
667 {
668 debugs(9, 3, status());
669
670 if (ctrl.replycode != 226 && ctrl.replycode != 250) {
671 debugs(9, DBG_IMPORTANT, "got FTP code " << ctrl.replycode <<
672 " after reading response data");
673 }
674
675 serverComplete();
676 }
677
678 void
679 Ftp::Relay::dataChannelConnected(const CommConnectCbParams &io)
680 {
681 debugs(9, 3, status());
682 data.opener = NULL;
683
684 if (io.flag != Comm::OK) {
685 debugs(9, 2, "failed to connect FTP server data channel");
686 forwardError(ERR_CONNECT_FAIL, io.xerrno);
687 return;
688 }
689
690 debugs(9, 2, "connected FTP server data channel: " << io.conn);
691
692 data.opened(io.conn, dataCloser());
693
694 sendCommand();
695 }
696
697 void
698 Ftp::Relay::scheduleReadControlReply()
699 {
700 Ftp::Client::scheduleReadControlReply(0);
701 }
702
703 AsyncJob::Pointer
704 Ftp::StartRelay(FwdState *const fwdState)
705 {
706 return AsyncJob::Start(new Ftp::Relay(fwdState));
707 }
708