]>
Commit | Line | Data |
---|---|---|
434a79b0 | 1 | /* |
77b1029d | 2 | * Copyright (C) 1996-2020 The Squid Software Foundation and contributors |
434a79b0 | 3 | * |
bbc27441 AJ |
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. | |
434a79b0 DK |
7 | */ |
8 | ||
bbc27441 AJ |
9 | /* DEBUG: section 09 File Transfer Protocol (FTP) */ |
10 | ||
434a79b0 | 11 | #include "squid.h" |
54a6c0cd | 12 | #include "anyp/PortCfg.h" |
92ae4c86 | 13 | #include "client_side.h" |
5517260a | 14 | #include "clients/forward.h" |
92ae4c86 | 15 | #include "clients/FtpClient.h" |
43446566 | 16 | #include "ftp/Elements.h" |
92ae4c86 | 17 | #include "ftp/Parsing.h" |
d3dddfb5 | 18 | #include "http/Stream.h" |
434a79b0 DK |
19 | #include "HttpHdrCc.h" |
20 | #include "HttpRequest.h" | |
65e41a45 | 21 | #include "sbuf/SBuf.h" |
27c841f6 | 22 | #include "servers/FtpServer.h" |
434a79b0 DK |
23 | #include "SquidTime.h" |
24 | #include "Store.h" | |
434a79b0 DK |
25 | #include "wordlist.h" |
26 | ||
27c841f6 AR |
27 | namespace Ftp |
28 | { | |
434a79b0 | 29 | |
e7ce227f | 30 | /// An FTP client receiving native FTP commands from our FTP server |
5517260a AR |
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 | |
434a79b0 | 34 | { |
5c2f68b7 AJ |
35 | CBDATA_CLASS(Relay); |
36 | ||
434a79b0 | 37 | public: |
5517260a AR |
38 | explicit Relay(FwdState *const fwdState); |
39 | virtual ~Relay(); | |
434a79b0 DK |
40 | |
41 | protected: | |
92ae4c86 AR |
42 | const Ftp::MasterState &master() const; |
43 | Ftp::MasterState &updateMaster(); | |
e7ce227f AR |
44 | Ftp::ServerState serverState() const { return master().serverState; } |
45 | void serverState(const Ftp::ServerState newState); | |
92ae4c86 | 46 | |
5517260a | 47 | /* Ftp::Client API */ |
777e8376 | 48 | virtual void failed(err_type error = ERR_NONE, int xerrno = 0, ErrorState *ftperr = nullptr); |
43446566 | 49 | virtual void dataChannelConnected(const CommConnectCbParams &io); |
5517260a | 50 | |
fccd4a86 | 51 | /* Client API */ |
5517260a | 52 | virtual void serverComplete(); |
434a79b0 | 53 | virtual void handleControlReply(); |
5517260a | 54 | virtual void processReplyBody(); |
434a79b0 | 55 | virtual void handleRequestBodyProducerAborted(); |
719c885c AR |
56 | virtual bool mayReadVirginReplyBody() const; |
57 | virtual void completeForwarding(); | |
92cfc72f | 58 | virtual bool abortOnData(const char *reason); |
5517260a AR |
59 | |
60 | /* AsyncJob API */ | |
61 | virtual void start(); | |
3238b9b6 | 62 | virtual void swanSong(); |
5517260a | 63 | |
434a79b0 DK |
64 | void forwardReply(); |
65 | void forwardError(err_type error = ERR_NONE, int xerrno = 0); | |
719c885c | 66 | void failedErrorMessage(err_type error, int xerrno); |
43446566 | 67 | HttpReply *createHttpReply(const Http::StatusCode httpStatus, const int64_t clen = 0); |
434a79b0 | 68 | void handleDataRequest(); |
a5d444a5 DK |
69 | void startDataDownload(); |
70 | void startDataUpload(); | |
54a6c0cd CT |
71 | bool startDirTracking(); |
72 | void stopDirTracking(); | |
73 | bool weAreTrackingDir() const {return savedReply.message != NULL;} | |
434a79b0 | 74 | |
5517260a | 75 | typedef void (Relay::*PreliminaryCb)(); |
434a79b0 DK |
76 | void forwardPreliminaryReply(const PreliminaryCb cb); |
77 | void proceedAfterPreliminaryReply(); | |
78 | PreliminaryCb thePreliminaryCb; | |
79 | ||
5517260a | 80 | typedef void (Relay::*SM_FUNC)(); |
434a79b0 | 81 | static const SM_FUNC SM_FUNCS[]; |
125894ba | 82 | void readGreeting(); |
434a79b0 DK |
83 | void sendCommand(); |
84 | void readReply(); | |
5f3898e2 | 85 | void readFeatReply(); |
434a79b0 DK |
86 | void readPasvReply(); |
87 | void readDataReply(); | |
88 | void readTransferDoneReply(); | |
000e664b | 89 | void readEpsvReply(); |
54a6c0cd CT |
90 | void readCwdOrCdupReply(); |
91 | void readUserOrPassReply(); | |
434a79b0 | 92 | |
434a79b0 | 93 | void scheduleReadControlReply(); |
3238b9b6 CT |
94 | |
95 | /// Inform Ftp::Server that we are done if originWaitInProgress | |
96 | void stopOriginWait(int code); | |
92cfc72f CT |
97 | |
98 | static void abort(void *d); // TODO: Capitalize this and FwdState::abort(). | |
434a79b0 | 99 | |
719c885c AR |
100 | bool forwardingCompleted; ///< completeForwarding() has been called |
101 | ||
3238b9b6 CT |
102 | /// whether we are between Ftp::Server::startWaitingForOrigin() and |
103 | /// Ftp::Server::stopWaitingForOrigin() calls | |
104 | bool originWaitInProgress; | |
105 | ||
54a6c0cd CT |
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 | |
434a79b0 DK |
112 | }; |
113 | ||
5517260a | 114 | } // namespace Ftp |
434a79b0 | 115 | |
5517260a AR |
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 | |
e7ce227f AR |
122 | NULL,/* &Ftp::Relay::readReply */ // SENT_TYPE |
123 | NULL,/* &Ftp::Relay::readReply */ // SENT_MDTM | |
124 | NULL,/* &Ftp::Relay::readReply */ // SENT_SIZE | |
000e664b AR |
125 | NULL, // SENT_EPRT |
126 | NULL, // SENT_PORT | |
5517260a AR |
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 | |
e7ce227f AR |
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 | |
5517260a AR |
138 | &Ftp::Relay::readTransferDoneReply, // READING_DATA |
139 | &Ftp::Relay::readReply, // WRITING_DATA | |
e7ce227f | 140 | NULL,/* &Ftp::Relay::readReply */ // SENT_MKDIR |
5517260a | 141 | &Ftp::Relay::readFeatReply, // SENT_FEAT |
e7ce227f | 142 | NULL,/* &Ftp::Relay::readPwdReply */ // SENT_PWD |
5517260a AR |
143 | &Ftp::Relay::readCwdOrCdupReply, // SENT_CDUP |
144 | &Ftp::Relay::readDataReply,// SENT_DATA_REQUEST | |
145 | &Ftp::Relay::readReply, // SENT_COMMAND | |
000e664b | 146 | NULL |
434a79b0 DK |
147 | }; |
148 | ||
5517260a | 149 | Ftp::Relay::Relay(FwdState *const fwdState): |
f53969cc SM |
150 | AsyncJob("Ftp::Relay"), |
151 | Ftp::Client(fwdState), | |
152 | thePreliminaryCb(NULL), | |
3238b9b6 CT |
153 | forwardingCompleted(false), |
154 | originWaitInProgress(false) | |
434a79b0 | 155 | { |
a5e978fd | 156 | savedReply.message = NULL; |
54a6c0cd CT |
157 | savedReply.lastCommand = NULL; |
158 | savedReply.lastReply = NULL; | |
159 | savedReply.replyCode = 0; | |
160 | ||
a8f874af AR |
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(); | |
92cfc72f CT |
164 | // TODO: Convert registerAbort() to use AsyncCall |
165 | entry->registerAbort(Ftp::Relay::abort, this); | |
434a79b0 DK |
166 | } |
167 | ||
5517260a | 168 | Ftp::Relay::~Relay() |
434a79b0 | 169 | { |
fccd4a86 | 170 | closeServer(); // TODO: move to clients/Client.cc? |
54a6c0cd CT |
171 | if (savedReply.message) |
172 | wordlistDestroy(&savedReply.message); | |
173 | ||
174 | xfree(savedReply.lastCommand); | |
175 | xfree(savedReply.lastReply); | |
434a79b0 DK |
176 | } |
177 | ||
178 | void | |
5517260a | 179 | Ftp::Relay::start() |
434a79b0 | 180 | { |
92ae4c86 | 181 | if (!master().clientReadGreeting) |
5517260a | 182 | Ftp::Client::start(); |
27c841f6 AR |
183 | else if (serverState() == fssHandleDataRequest || |
184 | serverState() == fssHandleUploadRequest) | |
434a79b0 DK |
185 | handleDataRequest(); |
186 | else | |
187 | sendCommand(); | |
188 | } | |
189 | ||
3238b9b6 CT |
190 | void |
191 | Ftp::Relay::swanSong() | |
192 | { | |
193 | stopOriginWait(0); | |
194 | Ftp::Client::swanSong(); | |
195 | } | |
196 | ||
29f23abf AR |
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 | |
5517260a | 200 | Ftp::Relay::serverComplete() |
29f23abf | 201 | { |
3238b9b6 CT |
202 | stopOriginWait(ctrl.replycode); |
203 | ||
89b1d7a2 AR |
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 { | |
801cfc26 CT |
214 | CallJobHere1(9, 4, mgr, |
215 | ConnStateData, | |
216 | notePinnedConnectionBecameIdle, | |
217 | ConnStateData::PinnedIdleContext(ctrl.conn, fwd->request)); | |
89b1d7a2 AR |
218 | ctrl.forget(); |
219 | } | |
220 | } | |
29f23abf | 221 | } |
5517260a | 222 | Ftp::Client::serverComplete(); |
29f23abf AR |
223 | } |
224 | ||
e7ce227f AR |
225 | /// Safely returns the master state, |
226 | /// with safety checks in case the Ftp::Server side of the master xact is gone. | |
92ae4c86 | 227 | Ftp::MasterState & |
5517260a | 228 | Ftp::Relay::updateMaster() |
92ae4c86 AR |
229 | { |
230 | CbcPointer<ConnStateData> &mgr = fwd->request->clientConnectionManager; | |
231 | if (mgr.valid()) { | |
232 | if (Ftp::Server *srv = dynamic_cast<Ftp::Server*>(mgr.get())) | |
aea65fec | 233 | return *srv->master; |
92ae4c86 AR |
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 | ||
e7ce227f | 242 | /// A const variant of updateMaster(). |
92ae4c86 | 243 | const Ftp::MasterState & |
5517260a | 244 | Ftp::Relay::master() const |
92ae4c86 | 245 | { |
e7ce227f | 246 | return const_cast<Ftp::Relay*>(this)->updateMaster(); // avoid code dupe |
434a79b0 DK |
247 | } |
248 | ||
e7ce227f | 249 | /// Changes server state and debugs about that important event. |
434a79b0 | 250 | void |
e7ce227f | 251 | Ftp::Relay::serverState(const Ftp::ServerState newState) |
434a79b0 | 252 | { |
92ae4c86 | 253 | Ftp::ServerState &cltState = updateMaster().serverState; |
719c885c AR |
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. | |
e7ce227f | 260 | * We complete forwarding when the response adaptation is over |
719c885c AR |
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 | |
5517260a | 267 | Ftp::Relay::completeForwarding() |
719c885c AR |
268 | { |
269 | debugs(9, 5, forwardingCompleted); | |
270 | if (forwardingCompleted) | |
271 | return; | |
272 | forwardingCompleted = true; | |
5517260a | 273 | Ftp::Client::completeForwarding(); |
434a79b0 DK |
274 | } |
275 | ||
276 | void | |
777e8376 | 277 | Ftp::Relay::failed(err_type error, int xerrno, ErrorState *ftpErr) |
434a79b0 DK |
278 | { |
279 | if (!doneWithServer()) | |
e7ce227f | 280 | serverState(fssError); |
434a79b0 | 281 | |
719c885c AR |
282 | // TODO: we need to customize ErrorState instead |
283 | if (entry->isEmpty()) | |
284 | failedErrorMessage(error, xerrno); // as a reply | |
285 | ||
777e8376 | 286 | Ftp::Client::failed(error, xerrno, ftpErr); |
434a79b0 DK |
287 | } |
288 | ||
289 | void | |
5517260a | 290 | Ftp::Relay::failedErrorMessage(err_type error, int xerrno) |
434a79b0 DK |
291 | { |
292 | const Http::StatusCode httpStatus = failedHttpStatus(error); | |
293 | HttpReply *const reply = createHttpReply(httpStatus); | |
294 | entry->replaceHttpReply(reply); | |
434a79b0 DK |
295 | fwd->request->detailError(error, xerrno); |
296 | } | |
297 | ||
298 | void | |
5517260a | 299 | Ftp::Relay::processReplyBody() |
434a79b0 | 300 | { |
e7ce227f | 301 | debugs(9, 3, status()); |
434a79b0 DK |
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 | */ | |
92cfc72f CT |
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"); | |
434a79b0 DK |
316 | return; |
317 | } | |
318 | ||
319 | #if USE_ADAPTATION | |
320 | ||
321 | if (adaptationAccessCheckPending) { | |
e7ce227f | 322 | debugs(9, 3, "returning due to adaptationAccessCheckPending"); |
434a79b0 DK |
323 | return; |
324 | } | |
325 | ||
326 | #endif | |
327 | ||
adaff124 DK |
328 | if (data.readBuf != NULL && data.readBuf->hasContent()) { |
329 | const mb_size_t csize = data.readBuf->contentSize(); | |
e7ce227f | 330 | debugs(9, 5, "writing " << csize << " bytes to the reply"); |
434a79b0 DK |
331 | addVirginReplyBody(data.readBuf->content(), csize); |
332 | data.readBuf->consume(csize); | |
333 | } | |
334 | ||
335 | entry->flush(); | |
336 | ||
337 | maybeReadVirginBody(); | |
338 | } | |
339 | ||
340 | void | |
5517260a | 341 | Ftp::Relay::handleControlReply() |
434a79b0 | 342 | { |
54a6c0cd CT |
343 | if (!request->clientConnectionManager.valid()) { |
344 | debugs(9, 5, "client connection gone"); | |
345 | closeServer(); | |
346 | return; | |
347 | } | |
348 | ||
5517260a | 349 | Ftp::Client::handleControlReply(); |
434a79b0 DK |
350 | if (ctrl.message == NULL) |
351 | return; // didn't get complete reply yet | |
352 | ||
29f23abf | 353 | assert(state < END); |
000e664b | 354 | assert(this->SM_FUNCS[state] != NULL); |
434a79b0 DK |
355 | (this->*SM_FUNCS[state])(); |
356 | } | |
357 | ||
358 | void | |
5517260a | 359 | Ftp::Relay::handleRequestBodyProducerAborted() |
434a79b0 | 360 | { |
fccd4a86 | 361 | ::Client::handleRequestBodyProducerAborted(); |
434a79b0 | 362 | |
a5d444a5 | 363 | failed(ERR_READ_ERROR); |
434a79b0 DK |
364 | } |
365 | ||
719c885c | 366 | bool |
5517260a | 367 | Ftp::Relay::mayReadVirginReplyBody() const |
719c885c AR |
368 | { |
369 | // TODO: move this method to the regular FTP server? | |
370 | return Comm::IsConnOpen(data.conn); | |
371 | } | |
372 | ||
434a79b0 | 373 | void |
5517260a | 374 | Ftp::Relay::forwardReply() |
434a79b0 DK |
375 | { |
376 | assert(entry->isEmpty()); | |
434a79b0 | 377 | |
15c82b83 | 378 | HttpReply *const reply = createHttpReply(Http::scNoContent); |
63df1d28 | 379 | reply->sources |= Http::Message::srcFtp; |
434a79b0 DK |
380 | |
381 | setVirginReply(reply); | |
382 | adaptOrFinalizeReply(); | |
383 | ||
434a79b0 DK |
384 | serverComplete(); |
385 | } | |
386 | ||
387 | void | |
5517260a | 388 | Ftp::Relay::forwardPreliminaryReply(const PreliminaryCb cb) |
434a79b0 | 389 | { |
e7ce227f | 390 | debugs(9, 5, "forwarding preliminary reply to client"); |
434a79b0 | 391 | |
92ae4c86 AR |
392 | // we must prevent concurrent ConnStateData::sendControlMsg() calls |
393 | Must(thePreliminaryCb == NULL); | |
434a79b0 DK |
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 | |
5517260a | 399 | typedef NullaryMemFunT<Relay> CbDialer; |
434a79b0 | 400 | const AsyncCall::Pointer call = JobCallback(11, 3, CbDialer, this, |
27c841f6 | 401 | Ftp::Relay::proceedAfterPreliminaryReply); |
434a79b0 DK |
402 | |
403 | CallJobHere1(9, 4, request->clientConnectionManager, ConnStateData, | |
404 | ConnStateData::sendControlMsg, HttpControlMsg(reply, call)); | |
405 | } | |
406 | ||
407 | void | |
5517260a | 408 | Ftp::Relay::proceedAfterPreliminaryReply() |
434a79b0 | 409 | { |
e7ce227f | 410 | debugs(9, 5, "proceeding after preliminary reply to client"); |
434a79b0 | 411 | |
92ae4c86 | 412 | Must(thePreliminaryCb != NULL); |
434a79b0 DK |
413 | const PreliminaryCb cb = thePreliminaryCb; |
414 | thePreliminaryCb = NULL; | |
415 | (this->*cb)(); | |
416 | } | |
417 | ||
418 | void | |
5517260a | 419 | Ftp::Relay::forwardError(err_type error, int xerrno) |
434a79b0 | 420 | { |
434a79b0 DK |
421 | failed(error, xerrno); |
422 | } | |
423 | ||
424 | HttpReply * | |
43446566 AR |
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); | |
a2c7f09a AR |
428 | if (ctrl.message) { |
429 | for (wordlist *W = ctrl.message; W && W->next; W = W->next) | |
789217a2 FC |
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 | |
a2c7f09a | 432 | } |
434a79b0 DK |
433 | return reply; |
434 | } | |
435 | ||
436 | void | |
5517260a | 437 | Ftp::Relay::handleDataRequest() |
434a79b0 | 438 | { |
92ae4c86 | 439 | data.addr(master().clientDataAddr); |
434a79b0 DK |
440 | connectDataChannel(); |
441 | } | |
442 | ||
443 | void | |
5517260a | 444 | Ftp::Relay::startDataDownload() |
434a79b0 DK |
445 | { |
446 | assert(Comm::IsConnOpen(data.conn)); | |
447 | ||
e7ce227f | 448 | debugs(9, 3, "begin data transfer from " << data.conn->remote << |
434a79b0 DK |
449 | " (" << data.conn->local << ")"); |
450 | ||
451 | HttpReply *const reply = createHttpReply(Http::scOkay, -1); | |
63df1d28 | 452 | reply->sources |= Http::Message::srcFtp; |
88df846b | 453 | |
434a79b0 DK |
454 | setVirginReply(reply); |
455 | adaptOrFinalizeReply(); | |
456 | ||
434a79b0 DK |
457 | maybeReadVirginBody(); |
458 | state = READING_DATA; | |
459 | } | |
460 | ||
a5d444a5 | 461 | void |
5517260a | 462 | Ftp::Relay::startDataUpload() |
a5d444a5 DK |
463 | { |
464 | assert(Comm::IsConnOpen(data.conn)); | |
465 | ||
e7ce227f | 466 | debugs(9, 3, "begin data transfer to " << data.conn->remote << |
a5d444a5 DK |
467 | " (" << data.conn->local << ")"); |
468 | ||
469 | if (!startRequestBodyFlow()) { // register to receive body data | |
470 | failed(); | |
471 | return; | |
472 | } | |
473 | ||
000e664b | 474 | state = WRITING_DATA; |
a5d444a5 DK |
475 | } |
476 | ||
434a79b0 | 477 | void |
5517260a | 478 | Ftp::Relay::readGreeting() |
434a79b0 | 479 | { |
92ae4c86 | 480 | assert(!master().clientReadGreeting); |
434a79b0 DK |
481 | |
482 | switch (ctrl.replycode) { | |
483 | case 220: | |
92ae4c86 | 484 | updateMaster().clientReadGreeting = true; |
e7ce227f AR |
485 | if (serverState() == fssBegin) |
486 | serverState(fssConnected); | |
29268829 | 487 | |
43446566 AR |
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. | |
29268829 DK |
491 | |
492 | start(); | |
434a79b0 DK |
493 | break; |
494 | case 120: | |
495 | if (NULL != ctrl.message) | |
496 | debugs(9, DBG_IMPORTANT, "FTP server is busy: " << ctrl.message->key); | |
5517260a | 497 | forwardPreliminaryReply(&Ftp::Relay::scheduleReadControlReply); |
434a79b0 DK |
498 | break; |
499 | default: | |
500 | failed(); | |
501 | break; | |
502 | } | |
503 | } | |
504 | ||
505 | void | |
5517260a | 506 | Ftp::Relay::sendCommand() |
434a79b0 | 507 | { |
789217a2 | 508 | if (!fwd->request->header.has(Http::HdrType::FTP_COMMAND)) { |
92cfc72f | 509 | abortAll("Internal error: FTP relay request with no command"); |
434a79b0 DK |
510 | return; |
511 | } | |
512 | ||
c8059ea9 | 513 | HttpHeader &header = fwd->request->header; |
789217a2 FC |
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 ¶ms = header.findEntry(Http::HdrType::FTP_ARGUMENTS)->value; | |
c8059ea9 DK |
518 | |
519 | if (params.size() > 0) | |
e7ce227f | 520 | debugs(9, 5, "command: " << cmd << ", parameters: " << params); |
434a79b0 | 521 | else |
e7ce227f | 522 | debugs(9, 5, "command: " << cmd << ", no parameters"); |
434a79b0 | 523 | |
e7ce227f | 524 | if (serverState() == fssHandlePasv || |
27c841f6 AR |
525 | serverState() == fssHandleEpsv || |
526 | serverState() == fssHandleEprt || | |
527 | serverState() == fssHandlePort) { | |
000e664b AR |
528 | sendPassive(); |
529 | return; | |
530 | } | |
531 | ||
1ab04517 | 532 | SBuf buf; |
c8059ea9 | 533 | if (params.size() > 0) |
1ab04517 | 534 | buf.Printf("%s %s%s", cmd.termedBuf(), params.termedBuf(), Ftp::crlf); |
434a79b0 | 535 | else |
1ab04517 | 536 | buf.Printf("%s%s", cmd.termedBuf(), Ftp::crlf); |
434a79b0 | 537 | |
1ab04517 | 538 | writeCommand(buf.c_str()); |
434a79b0 | 539 | |
cff221ee | 540 | state = |
e7ce227f AR |
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 : | |
434a79b0 | 548 | SENT_COMMAND; |
3238b9b6 CT |
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 | } | |
434a79b0 DK |
562 | } |
563 | ||
564 | void | |
5517260a | 565 | Ftp::Relay::readReply() |
434a79b0 | 566 | { |
e7ce227f AR |
567 | assert(serverState() == fssConnected || |
568 | serverState() == fssHandleUploadRequest); | |
434a79b0 | 569 | |
4f1c93a7 | 570 | if (Is1xx(ctrl.replycode)) |
5517260a | 571 | forwardPreliminaryReply(&Ftp::Relay::scheduleReadControlReply); |
434a79b0 DK |
572 | else |
573 | forwardReply(); | |
574 | } | |
575 | ||
5f3898e2 | 576 | void |
5517260a | 577 | Ftp::Relay::readFeatReply() |
5f3898e2 | 578 | { |
e7ce227f | 579 | assert(serverState() == fssHandleFeat); |
5f3898e2 | 580 | |
4f1c93a7 | 581 | if (Is1xx(ctrl.replycode)) |
5f3898e2 AR |
582 | return; // ignore preliminary replies |
583 | ||
584 | forwardReply(); | |
585 | } | |
586 | ||
434a79b0 | 587 | void |
5517260a | 588 | Ftp::Relay::readPasvReply() |
434a79b0 | 589 | { |
e7ce227f | 590 | assert(serverState() == fssHandlePasv || serverState() == fssHandleEpsv || serverState() == fssHandlePort || serverState() == fssHandleEprt); |
434a79b0 | 591 | |
4f1c93a7 | 592 | if (Is1xx(ctrl.replycode)) |
434a79b0 DK |
593 | return; // ignore preliminary replies |
594 | ||
92ae4c86 | 595 | if (handlePasvReply(updateMaster().clientDataAddr)) |
434a79b0 | 596 | forwardReply(); |
73950ceb | 597 | else |
434a79b0 DK |
598 | forwardError(); |
599 | } | |
600 | ||
cff221ee | 601 | void |
5517260a | 602 | Ftp::Relay::readEpsvReply() |
cff221ee | 603 | { |
4f1c93a7 | 604 | if (Is1xx(ctrl.replycode)) |
cff221ee AR |
605 | return; // ignore preliminary replies |
606 | ||
92ae4c86 | 607 | if (handleEpsvReply(updateMaster().clientDataAddr)) { |
000e664b AR |
608 | if (ctrl.message == NULL) |
609 | return; // didn't get complete reply yet | |
610 | ||
cff221ee | 611 | forwardReply(); |
000e664b | 612 | } else |
cff221ee AR |
613 | forwardError(); |
614 | } | |
615 | ||
434a79b0 | 616 | void |
5517260a | 617 | Ftp::Relay::readDataReply() |
434a79b0 | 618 | { |
e7ce227f AR |
619 | assert(serverState() == fssHandleDataRequest || |
620 | serverState() == fssHandleUploadRequest); | |
a5d444a5 | 621 | |
6af7d28d | 622 | if (ctrl.replycode == 125 || ctrl.replycode == 150) { |
e7ce227f | 623 | if (serverState() == fssHandleDataRequest) |
5517260a | 624 | forwardPreliminaryReply(&Ftp::Relay::startDataDownload); |
ec69bdb2 CT |
625 | else if (fwd->request->forcedBodyContinuation /*&& serverState() == fssHandleUploadRequest*/) |
626 | startDataUpload(); | |
f53969cc | 627 | else // serverState() == fssHandleUploadRequest |
5517260a | 628 | forwardPreliminaryReply(&Ftp::Relay::startDataUpload); |
a5d444a5 | 629 | } else |
434a79b0 | 630 | forwardReply(); |
54a6c0cd CT |
631 | } |
632 | ||
633 | bool | |
5517260a | 634 | Ftp::Relay::startDirTracking() |
54a6c0cd CT |
635 | { |
636 | if (!fwd->request->clientConnectionManager->port->ftp_track_dirs) | |
637 | return false; | |
638 | ||
e7ce227f | 639 | debugs(9, 5, "start directory tracking"); |
54a6c0cd CT |
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 | |
5517260a | 654 | Ftp::Relay::stopDirTracking() |
54a6c0cd | 655 | { |
e7ce227f | 656 | debugs(9, 5, "got code from pwd: " << ctrl.replycode << ", msg: " << ctrl.last_reply); |
54a6c0cd | 657 | |
92ae4c86 AR |
658 | if (ctrl.replycode == 257) |
659 | updateMaster().workingDir = Ftp::UnescapeDoubleQuoted(ctrl.last_reply); | |
54a6c0cd CT |
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 | |
5517260a | 676 | Ftp::Relay::readCwdOrCdupReply() |
54a6c0cd | 677 | { |
e7ce227f AR |
678 | assert(serverState() == fssHandleCwd || |
679 | serverState() == fssHandleCdup); | |
54a6c0cd | 680 | |
e7ce227f | 681 | debugs(9, 5, "got code " << ctrl.replycode << ", msg: " << ctrl.last_reply); |
54a6c0cd | 682 | |
4f1c93a7 | 683 | if (Is1xx(ctrl.replycode)) |
54a6c0cd CT |
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 | |
5517260a | 695 | Ftp::Relay::readUserOrPassReply() |
54a6c0cd | 696 | { |
4f1c93a7 | 697 | if (Is1xx(ctrl.replycode)) |
54a6c0cd CT |
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(); | |
434a79b0 DK |
708 | } |
709 | ||
710 | void | |
5517260a | 711 | Ftp::Relay::readTransferDoneReply() |
434a79b0 | 712 | { |
e7ce227f | 713 | debugs(9, 3, status()); |
434a79b0 DK |
714 | |
715 | if (ctrl.replycode != 226 && ctrl.replycode != 250) { | |
e7ce227f AR |
716 | debugs(9, DBG_IMPORTANT, "got FTP code " << ctrl.replycode << |
717 | " after reading response data"); | |
434a79b0 DK |
718 | } |
719 | ||
3238b9b6 CT |
720 | debugs(9, 2, "Complete data downloading"); |
721 | ||
722 | serverComplete(); | |
434a79b0 DK |
723 | } |
724 | ||
725 | void | |
43446566 | 726 | Ftp::Relay::dataChannelConnected(const CommConnectCbParams &io) |
434a79b0 | 727 | { |
e7ce227f | 728 | debugs(9, 3, status()); |
434a79b0 DK |
729 | data.opener = NULL; |
730 | ||
43446566 | 731 | if (io.flag != Comm::OK) { |
e7ce227f | 732 | debugs(9, 2, "failed to connect FTP server data channel"); |
43446566 | 733 | forwardError(ERR_CONNECT_FAIL, io.xerrno); |
434a79b0 DK |
734 | return; |
735 | } | |
736 | ||
43446566 | 737 | debugs(9, 2, "connected FTP server data channel: " << io.conn); |
434a79b0 | 738 | |
43446566 | 739 | data.opened(io.conn, dataCloser()); |
434a79b0 DK |
740 | |
741 | sendCommand(); | |
742 | } | |
743 | ||
744 | void | |
5517260a | 745 | Ftp::Relay::scheduleReadControlReply() |
434a79b0 | 746 | { |
5517260a | 747 | Ftp::Client::scheduleReadControlReply(0); |
434a79b0 DK |
748 | } |
749 | ||
92cfc72f CT |
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 | ||
3238b9b6 CT |
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 | ||
92cfc72f CT |
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 | ||
5517260a AR |
797 | AsyncJob::Pointer |
798 | Ftp::StartRelay(FwdState *const fwdState) | |
434a79b0 | 799 | { |
5517260a | 800 | return AsyncJob::Start(new Ftp::Relay(fwdState)); |
434a79b0 | 801 | } |
f53969cc | 802 |