]>
Commit | Line | Data |
---|---|---|
434a79b0 | 1 | /* |
bf95c10a | 2 | * Copyright (C) 1996-2022 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" |
7e9f330d | 13 | #include "base/AsyncCbdataCalls.h" |
92ae4c86 | 14 | #include "client_side.h" |
5517260a | 15 | #include "clients/forward.h" |
92ae4c86 | 16 | #include "clients/FtpClient.h" |
83b053a0 | 17 | #include "error/SysErrorDetail.h" |
43446566 | 18 | #include "ftp/Elements.h" |
92ae4c86 | 19 | #include "ftp/Parsing.h" |
d3dddfb5 | 20 | #include "http/Stream.h" |
434a79b0 DK |
21 | #include "HttpHdrCc.h" |
22 | #include "HttpRequest.h" | |
65e41a45 | 23 | #include "sbuf/SBuf.h" |
27c841f6 | 24 | #include "servers/FtpServer.h" |
434a79b0 DK |
25 | #include "SquidTime.h" |
26 | #include "Store.h" | |
434a79b0 DK |
27 | #include "wordlist.h" |
28 | ||
27c841f6 AR |
29 | namespace Ftp |
30 | { | |
434a79b0 | 31 | |
e7ce227f | 32 | /// An FTP client receiving native FTP commands from our FTP server |
5517260a AR |
33 | /// (Ftp::Server), forwarding them to the next FTP hop, |
34 | /// and then relaying FTP replies back to our FTP server. | |
35 | class Relay: public Ftp::Client | |
434a79b0 | 36 | { |
5c2f68b7 AJ |
37 | CBDATA_CLASS(Relay); |
38 | ||
434a79b0 | 39 | public: |
5517260a AR |
40 | explicit Relay(FwdState *const fwdState); |
41 | virtual ~Relay(); | |
434a79b0 DK |
42 | |
43 | protected: | |
92ae4c86 AR |
44 | const Ftp::MasterState &master() const; |
45 | Ftp::MasterState &updateMaster(); | |
e7ce227f AR |
46 | Ftp::ServerState serverState() const { return master().serverState; } |
47 | void serverState(const Ftp::ServerState newState); | |
92ae4c86 | 48 | |
5517260a | 49 | /* Ftp::Client API */ |
777e8376 | 50 | virtual void failed(err_type error = ERR_NONE, int xerrno = 0, ErrorState *ftperr = nullptr); |
43446566 | 51 | virtual void dataChannelConnected(const CommConnectCbParams &io); |
5517260a | 52 | |
fccd4a86 | 53 | /* Client API */ |
5517260a | 54 | virtual void serverComplete(); |
434a79b0 | 55 | virtual void handleControlReply(); |
5517260a | 56 | virtual void processReplyBody(); |
434a79b0 | 57 | virtual void handleRequestBodyProducerAborted(); |
719c885c AR |
58 | virtual bool mayReadVirginReplyBody() const; |
59 | virtual void completeForwarding(); | |
92cfc72f | 60 | virtual bool abortOnData(const char *reason); |
5517260a AR |
61 | |
62 | /* AsyncJob API */ | |
63 | virtual void start(); | |
3238b9b6 | 64 | virtual void swanSong(); |
5517260a | 65 | |
434a79b0 DK |
66 | void forwardReply(); |
67 | void forwardError(err_type error = ERR_NONE, int xerrno = 0); | |
719c885c | 68 | void failedErrorMessage(err_type error, int xerrno); |
43446566 | 69 | HttpReply *createHttpReply(const Http::StatusCode httpStatus, const int64_t clen = 0); |
434a79b0 | 70 | void handleDataRequest(); |
a5d444a5 DK |
71 | void startDataDownload(); |
72 | void startDataUpload(); | |
54a6c0cd CT |
73 | bool startDirTracking(); |
74 | void stopDirTracking(); | |
75 | bool weAreTrackingDir() const {return savedReply.message != NULL;} | |
434a79b0 | 76 | |
5517260a | 77 | typedef void (Relay::*PreliminaryCb)(); |
434a79b0 DK |
78 | void forwardPreliminaryReply(const PreliminaryCb cb); |
79 | void proceedAfterPreliminaryReply(); | |
80 | PreliminaryCb thePreliminaryCb; | |
81 | ||
5517260a | 82 | typedef void (Relay::*SM_FUNC)(); |
434a79b0 | 83 | static const SM_FUNC SM_FUNCS[]; |
125894ba | 84 | void readGreeting(); |
434a79b0 DK |
85 | void sendCommand(); |
86 | void readReply(); | |
5f3898e2 | 87 | void readFeatReply(); |
434a79b0 DK |
88 | void readPasvReply(); |
89 | void readDataReply(); | |
90 | void readTransferDoneReply(); | |
000e664b | 91 | void readEpsvReply(); |
54a6c0cd CT |
92 | void readCwdOrCdupReply(); |
93 | void readUserOrPassReply(); | |
434a79b0 | 94 | |
434a79b0 | 95 | void scheduleReadControlReply(); |
3238b9b6 CT |
96 | |
97 | /// Inform Ftp::Server that we are done if originWaitInProgress | |
98 | void stopOriginWait(int code); | |
7e9f330d EB |
99 | /// called by Store if the entry is no longer usable |
100 | static void HandleStoreAbort(Relay *); | |
434a79b0 | 101 | |
719c885c AR |
102 | bool forwardingCompleted; ///< completeForwarding() has been called |
103 | ||
3238b9b6 CT |
104 | /// whether we are between Ftp::Server::startWaitingForOrigin() and |
105 | /// Ftp::Server::stopWaitingForOrigin() calls | |
106 | bool originWaitInProgress; | |
107 | ||
54a6c0cd CT |
108 | struct { |
109 | wordlist *message; ///< reply message, one wordlist entry per message line | |
110 | char *lastCommand; ///< the command caused the reply | |
111 | char *lastReply; ///< last line of reply: reply status plus message | |
112 | int replyCode; ///< the reply status | |
113 | } savedReply; ///< set and delayed while we are tracking using PWD | |
434a79b0 DK |
114 | }; |
115 | ||
5517260a | 116 | } // namespace Ftp |
434a79b0 | 117 | |
5517260a AR |
118 | CBDATA_NAMESPACED_CLASS_INIT(Ftp, Relay); |
119 | ||
120 | const Ftp::Relay::SM_FUNC Ftp::Relay::SM_FUNCS[] = { | |
121 | &Ftp::Relay::readGreeting, // BEGIN | |
122 | &Ftp::Relay::readUserOrPassReply, // SENT_USER | |
123 | &Ftp::Relay::readUserOrPassReply, // SENT_PASS | |
e7ce227f AR |
124 | NULL,/* &Ftp::Relay::readReply */ // SENT_TYPE |
125 | NULL,/* &Ftp::Relay::readReply */ // SENT_MDTM | |
126 | NULL,/* &Ftp::Relay::readReply */ // SENT_SIZE | |
000e664b AR |
127 | NULL, // SENT_EPRT |
128 | NULL, // SENT_PORT | |
5517260a AR |
129 | &Ftp::Relay::readEpsvReply, // SENT_EPSV_ALL |
130 | &Ftp::Relay::readEpsvReply, // SENT_EPSV_1 | |
131 | &Ftp::Relay::readEpsvReply, // SENT_EPSV_2 | |
132 | &Ftp::Relay::readPasvReply, // SENT_PASV | |
133 | &Ftp::Relay::readCwdOrCdupReply, // SENT_CWD | |
e7ce227f AR |
134 | NULL,/* &Ftp::Relay::readDataReply, */ // SENT_LIST |
135 | NULL,/* &Ftp::Relay::readDataReply, */ // SENT_NLST | |
136 | NULL,/* &Ftp::Relay::readReply */ // SENT_REST | |
137 | NULL,/* &Ftp::Relay::readDataReply */ // SENT_RETR | |
138 | NULL,/* &Ftp::Relay::readReply */ // SENT_STOR | |
139 | NULL,/* &Ftp::Relay::readReply */ // SENT_QUIT | |
5517260a AR |
140 | &Ftp::Relay::readTransferDoneReply, // READING_DATA |
141 | &Ftp::Relay::readReply, // WRITING_DATA | |
e7ce227f | 142 | NULL,/* &Ftp::Relay::readReply */ // SENT_MKDIR |
5517260a | 143 | &Ftp::Relay::readFeatReply, // SENT_FEAT |
e7ce227f | 144 | NULL,/* &Ftp::Relay::readPwdReply */ // SENT_PWD |
5517260a AR |
145 | &Ftp::Relay::readCwdOrCdupReply, // SENT_CDUP |
146 | &Ftp::Relay::readDataReply,// SENT_DATA_REQUEST | |
147 | &Ftp::Relay::readReply, // SENT_COMMAND | |
000e664b | 148 | NULL |
434a79b0 DK |
149 | }; |
150 | ||
5517260a | 151 | Ftp::Relay::Relay(FwdState *const fwdState): |
f53969cc SM |
152 | AsyncJob("Ftp::Relay"), |
153 | Ftp::Client(fwdState), | |
154 | thePreliminaryCb(NULL), | |
3238b9b6 CT |
155 | forwardingCompleted(false), |
156 | originWaitInProgress(false) | |
434a79b0 | 157 | { |
a5e978fd | 158 | savedReply.message = NULL; |
54a6c0cd CT |
159 | savedReply.lastCommand = NULL; |
160 | savedReply.lastReply = NULL; | |
161 | savedReply.replyCode = 0; | |
162 | ||
8a07d123 AR |
163 | // Prevent the future response from becoming public and being shared/cached |
164 | // because FTP does not support response cachability and freshness checks. | |
a8f874af | 165 | entry->releaseRequest(); |
7e9f330d EB |
166 | AsyncCall::Pointer call = asyncCall(9, 4, "Ftp::Relay::Abort", cbdataDialer(&Relay::HandleStoreAbort, this)); |
167 | entry->registerAbortCallback(call); | |
434a79b0 DK |
168 | } |
169 | ||
5517260a | 170 | Ftp::Relay::~Relay() |
434a79b0 | 171 | { |
7e9f330d EB |
172 | entry->unregisterAbortCallback("Ftp::Relay object destructed"); |
173 | // Client, our parent, calls entry->unlock(). | |
174 | // Client does not currently un/registerAbortCallback() because | |
175 | // FwdState does that for other Client kids; \see FwdState::start(). | |
176 | ||
fccd4a86 | 177 | closeServer(); // TODO: move to clients/Client.cc? |
54a6c0cd CT |
178 | if (savedReply.message) |
179 | wordlistDestroy(&savedReply.message); | |
180 | ||
181 | xfree(savedReply.lastCommand); | |
182 | xfree(savedReply.lastReply); | |
434a79b0 DK |
183 | } |
184 | ||
185 | void | |
5517260a | 186 | Ftp::Relay::start() |
434a79b0 | 187 | { |
92ae4c86 | 188 | if (!master().clientReadGreeting) |
5517260a | 189 | Ftp::Client::start(); |
27c841f6 AR |
190 | else if (serverState() == fssHandleDataRequest || |
191 | serverState() == fssHandleUploadRequest) | |
434a79b0 DK |
192 | handleDataRequest(); |
193 | else | |
194 | sendCommand(); | |
195 | } | |
196 | ||
3238b9b6 CT |
197 | void |
198 | Ftp::Relay::swanSong() | |
199 | { | |
200 | stopOriginWait(0); | |
201 | Ftp::Client::swanSong(); | |
202 | } | |
203 | ||
29f23abf AR |
204 | /// Keep control connection for future requests, after we are done with it. |
205 | /// Similar to COMPLETE_PERSISTENT_MSG handling in http.cc. | |
206 | void | |
5517260a | 207 | Ftp::Relay::serverComplete() |
29f23abf | 208 | { |
3238b9b6 CT |
209 | stopOriginWait(ctrl.replycode); |
210 | ||
89b1d7a2 AR |
211 | CbcPointer<ConnStateData> &mgr = fwd->request->clientConnectionManager; |
212 | if (mgr.valid()) { | |
213 | if (Comm::IsConnOpen(ctrl.conn)) { | |
214 | debugs(9, 7, "completing FTP server " << ctrl.conn << | |
215 | " after " << ctrl.replycode); | |
216 | fwd->unregister(ctrl.conn); | |
217 | if (ctrl.replycode == 221) { // Server sends FTP 221 before closing | |
218 | mgr->unpinConnection(false); | |
219 | ctrl.close(); | |
220 | } else { | |
801cfc26 CT |
221 | CallJobHere1(9, 4, mgr, |
222 | ConnStateData, | |
223 | notePinnedConnectionBecameIdle, | |
224 | ConnStateData::PinnedIdleContext(ctrl.conn, fwd->request)); | |
89b1d7a2 AR |
225 | ctrl.forget(); |
226 | } | |
227 | } | |
29f23abf | 228 | } |
5517260a | 229 | Ftp::Client::serverComplete(); |
29f23abf AR |
230 | } |
231 | ||
e7ce227f AR |
232 | /// Safely returns the master state, |
233 | /// with safety checks in case the Ftp::Server side of the master xact is gone. | |
92ae4c86 | 234 | Ftp::MasterState & |
5517260a | 235 | Ftp::Relay::updateMaster() |
92ae4c86 AR |
236 | { |
237 | CbcPointer<ConnStateData> &mgr = fwd->request->clientConnectionManager; | |
238 | if (mgr.valid()) { | |
239 | if (Ftp::Server *srv = dynamic_cast<Ftp::Server*>(mgr.get())) | |
aea65fec | 240 | return *srv->master; |
92ae4c86 AR |
241 | } |
242 | // this code will not be necessary once the master is inside MasterXaction | |
243 | debugs(9, 3, "our server side is gone: " << mgr); | |
244 | static Ftp::MasterState Master; | |
245 | Master = Ftp::MasterState(); | |
246 | return Master; | |
247 | } | |
248 | ||
e7ce227f | 249 | /// A const variant of updateMaster(). |
92ae4c86 | 250 | const Ftp::MasterState & |
5517260a | 251 | Ftp::Relay::master() const |
92ae4c86 | 252 | { |
e7ce227f | 253 | return const_cast<Ftp::Relay*>(this)->updateMaster(); // avoid code dupe |
434a79b0 DK |
254 | } |
255 | ||
e7ce227f | 256 | /// Changes server state and debugs about that important event. |
434a79b0 | 257 | void |
e7ce227f | 258 | Ftp::Relay::serverState(const Ftp::ServerState newState) |
434a79b0 | 259 | { |
92ae4c86 | 260 | Ftp::ServerState &cltState = updateMaster().serverState; |
719c885c AR |
261 | debugs(9, 3, "client state was " << cltState << " now: " << newState); |
262 | cltState = newState; | |
263 | } | |
264 | ||
265 | /** | |
266 | * Ensure we do not double-complete on the forward entry. | |
e7ce227f | 267 | * We complete forwarding when the response adaptation is over |
719c885c AR |
268 | * (but we may still be waiting for 226 from the FTP server) and |
269 | * also when we get that 226 from the server (and adaptation is done). | |
270 | * | |
9837567d | 271 | * TODO: Rewrite FwdState to ignore double completion? |
719c885c AR |
272 | */ |
273 | void | |
5517260a | 274 | Ftp::Relay::completeForwarding() |
719c885c AR |
275 | { |
276 | debugs(9, 5, forwardingCompleted); | |
277 | if (forwardingCompleted) | |
278 | return; | |
279 | forwardingCompleted = true; | |
5517260a | 280 | Ftp::Client::completeForwarding(); |
434a79b0 DK |
281 | } |
282 | ||
283 | void | |
777e8376 | 284 | Ftp::Relay::failed(err_type error, int xerrno, ErrorState *ftpErr) |
434a79b0 DK |
285 | { |
286 | if (!doneWithServer()) | |
e7ce227f | 287 | serverState(fssError); |
434a79b0 | 288 | |
719c885c AR |
289 | // TODO: we need to customize ErrorState instead |
290 | if (entry->isEmpty()) | |
291 | failedErrorMessage(error, xerrno); // as a reply | |
292 | ||
777e8376 | 293 | Ftp::Client::failed(error, xerrno, ftpErr); |
434a79b0 DK |
294 | } |
295 | ||
296 | void | |
5517260a | 297 | Ftp::Relay::failedErrorMessage(err_type error, int xerrno) |
434a79b0 DK |
298 | { |
299 | const Http::StatusCode httpStatus = failedHttpStatus(error); | |
300 | HttpReply *const reply = createHttpReply(httpStatus); | |
301 | entry->replaceHttpReply(reply); | |
83b053a0 | 302 | fwd->request->detailError(error, SysErrorDetail::NewIfAny(xerrno)); |
434a79b0 DK |
303 | } |
304 | ||
305 | void | |
5517260a | 306 | Ftp::Relay::processReplyBody() |
434a79b0 | 307 | { |
e7ce227f | 308 | debugs(9, 3, status()); |
434a79b0 DK |
309 | |
310 | if (EBIT_TEST(entry->flags, ENTRY_ABORTED)) { | |
311 | /* | |
312 | * probably was aborted because content length exceeds one | |
313 | * of the maximum size limits. | |
314 | */ | |
92cfc72f CT |
315 | abortOnData("entry aborted after calling appendSuccessHeader()"); |
316 | return; | |
317 | } | |
318 | ||
319 | if (master().userDataDone) { | |
320 | // Squid-to-client data transfer done. Abort data transfer on our | |
321 | // side to allow new commands from ftp client | |
322 | abortOnData("Squid-to-client data connection is closed"); | |
434a79b0 DK |
323 | return; |
324 | } | |
325 | ||
326 | #if USE_ADAPTATION | |
327 | ||
328 | if (adaptationAccessCheckPending) { | |
e7ce227f | 329 | debugs(9, 3, "returning due to adaptationAccessCheckPending"); |
434a79b0 DK |
330 | return; |
331 | } | |
332 | ||
333 | #endif | |
334 | ||
adaff124 DK |
335 | if (data.readBuf != NULL && data.readBuf->hasContent()) { |
336 | const mb_size_t csize = data.readBuf->contentSize(); | |
e7ce227f | 337 | debugs(9, 5, "writing " << csize << " bytes to the reply"); |
434a79b0 DK |
338 | addVirginReplyBody(data.readBuf->content(), csize); |
339 | data.readBuf->consume(csize); | |
340 | } | |
341 | ||
342 | entry->flush(); | |
343 | ||
344 | maybeReadVirginBody(); | |
345 | } | |
346 | ||
347 | void | |
5517260a | 348 | Ftp::Relay::handleControlReply() |
434a79b0 | 349 | { |
54a6c0cd CT |
350 | if (!request->clientConnectionManager.valid()) { |
351 | debugs(9, 5, "client connection gone"); | |
352 | closeServer(); | |
353 | return; | |
354 | } | |
355 | ||
5517260a | 356 | Ftp::Client::handleControlReply(); |
434a79b0 DK |
357 | if (ctrl.message == NULL) |
358 | return; // didn't get complete reply yet | |
359 | ||
29f23abf | 360 | assert(state < END); |
000e664b | 361 | assert(this->SM_FUNCS[state] != NULL); |
434a79b0 DK |
362 | (this->*SM_FUNCS[state])(); |
363 | } | |
364 | ||
365 | void | |
5517260a | 366 | Ftp::Relay::handleRequestBodyProducerAborted() |
434a79b0 | 367 | { |
fccd4a86 | 368 | ::Client::handleRequestBodyProducerAborted(); |
434a79b0 | 369 | |
a5d444a5 | 370 | failed(ERR_READ_ERROR); |
434a79b0 DK |
371 | } |
372 | ||
719c885c | 373 | bool |
5517260a | 374 | Ftp::Relay::mayReadVirginReplyBody() const |
719c885c AR |
375 | { |
376 | // TODO: move this method to the regular FTP server? | |
377 | return Comm::IsConnOpen(data.conn); | |
378 | } | |
379 | ||
434a79b0 | 380 | void |
5517260a | 381 | Ftp::Relay::forwardReply() |
434a79b0 DK |
382 | { |
383 | assert(entry->isEmpty()); | |
434a79b0 | 384 | |
15c82b83 | 385 | HttpReply *const reply = createHttpReply(Http::scNoContent); |
63df1d28 | 386 | reply->sources |= Http::Message::srcFtp; |
434a79b0 DK |
387 | |
388 | setVirginReply(reply); | |
ba3fe8d9 | 389 | markParsedVirginReplyAsWhole("Ftp::Relay::handleControlReply() does not forward partial replies"); |
434a79b0 DK |
390 | adaptOrFinalizeReply(); |
391 | ||
434a79b0 DK |
392 | serverComplete(); |
393 | } | |
394 | ||
395 | void | |
5517260a | 396 | Ftp::Relay::forwardPreliminaryReply(const PreliminaryCb cb) |
434a79b0 | 397 | { |
e7ce227f | 398 | debugs(9, 5, "forwarding preliminary reply to client"); |
434a79b0 | 399 | |
92ae4c86 AR |
400 | // we must prevent concurrent ConnStateData::sendControlMsg() calls |
401 | Must(thePreliminaryCb == NULL); | |
434a79b0 DK |
402 | thePreliminaryCb = cb; |
403 | ||
404 | const HttpReply::Pointer reply = createHttpReply(Http::scContinue); | |
405 | ||
406 | // the Sink will use this to call us back after writing 1xx to the client | |
5517260a | 407 | typedef NullaryMemFunT<Relay> CbDialer; |
434a79b0 | 408 | const AsyncCall::Pointer call = JobCallback(11, 3, CbDialer, this, |
27c841f6 | 409 | Ftp::Relay::proceedAfterPreliminaryReply); |
434a79b0 DK |
410 | |
411 | CallJobHere1(9, 4, request->clientConnectionManager, ConnStateData, | |
412 | ConnStateData::sendControlMsg, HttpControlMsg(reply, call)); | |
413 | } | |
414 | ||
415 | void | |
5517260a | 416 | Ftp::Relay::proceedAfterPreliminaryReply() |
434a79b0 | 417 | { |
e7ce227f | 418 | debugs(9, 5, "proceeding after preliminary reply to client"); |
434a79b0 | 419 | |
92ae4c86 | 420 | Must(thePreliminaryCb != NULL); |
434a79b0 DK |
421 | const PreliminaryCb cb = thePreliminaryCb; |
422 | thePreliminaryCb = NULL; | |
423 | (this->*cb)(); | |
424 | } | |
425 | ||
426 | void | |
5517260a | 427 | Ftp::Relay::forwardError(err_type error, int xerrno) |
434a79b0 | 428 | { |
434a79b0 DK |
429 | failed(error, xerrno); |
430 | } | |
431 | ||
432 | HttpReply * | |
43446566 AR |
433 | Ftp::Relay::createHttpReply(const Http::StatusCode httpStatus, const int64_t clen) |
434 | { | |
435 | HttpReply *const reply = Ftp::HttpReplyWrapper(ctrl.replycode, ctrl.last_reply, httpStatus, clen); | |
a2c7f09a AR |
436 | if (ctrl.message) { |
437 | for (wordlist *W = ctrl.message; W && W->next; W = W->next) | |
789217a2 FC |
438 | reply->header.putStr(Http::HdrType::FTP_PRE, httpHeaderQuoteString(W->key).c_str()); |
439 | // no hdrCacheInit() is needed for after Http::HdrType::FTP_PRE addition | |
a2c7f09a | 440 | } |
434a79b0 DK |
441 | return reply; |
442 | } | |
443 | ||
444 | void | |
5517260a | 445 | Ftp::Relay::handleDataRequest() |
434a79b0 | 446 | { |
92ae4c86 | 447 | data.addr(master().clientDataAddr); |
434a79b0 DK |
448 | connectDataChannel(); |
449 | } | |
450 | ||
451 | void | |
5517260a | 452 | Ftp::Relay::startDataDownload() |
434a79b0 DK |
453 | { |
454 | assert(Comm::IsConnOpen(data.conn)); | |
455 | ||
e7ce227f | 456 | debugs(9, 3, "begin data transfer from " << data.conn->remote << |
434a79b0 DK |
457 | " (" << data.conn->local << ")"); |
458 | ||
459 | HttpReply *const reply = createHttpReply(Http::scOkay, -1); | |
63df1d28 | 460 | reply->sources |= Http::Message::srcFtp; |
88df846b | 461 | |
434a79b0 DK |
462 | setVirginReply(reply); |
463 | adaptOrFinalizeReply(); | |
464 | ||
434a79b0 DK |
465 | maybeReadVirginBody(); |
466 | state = READING_DATA; | |
467 | } | |
468 | ||
a5d444a5 | 469 | void |
5517260a | 470 | Ftp::Relay::startDataUpload() |
a5d444a5 DK |
471 | { |
472 | assert(Comm::IsConnOpen(data.conn)); | |
473 | ||
e7ce227f | 474 | debugs(9, 3, "begin data transfer to " << data.conn->remote << |
a5d444a5 DK |
475 | " (" << data.conn->local << ")"); |
476 | ||
477 | if (!startRequestBodyFlow()) { // register to receive body data | |
478 | failed(); | |
479 | return; | |
480 | } | |
481 | ||
000e664b | 482 | state = WRITING_DATA; |
a5d444a5 DK |
483 | } |
484 | ||
434a79b0 | 485 | void |
5517260a | 486 | Ftp::Relay::readGreeting() |
434a79b0 | 487 | { |
92ae4c86 | 488 | assert(!master().clientReadGreeting); |
434a79b0 DK |
489 | |
490 | switch (ctrl.replycode) { | |
491 | case 220: | |
92ae4c86 | 492 | updateMaster().clientReadGreeting = true; |
e7ce227f AR |
493 | if (serverState() == fssBegin) |
494 | serverState(fssConnected); | |
29268829 | 495 | |
43446566 AR |
496 | // Do not forward server greeting to the user because our FTP Server |
497 | // has greeted the user already. Also, an original origin greeting may | |
498 | // confuse a user that has changed the origin mid-air. | |
29268829 DK |
499 | |
500 | start(); | |
434a79b0 DK |
501 | break; |
502 | case 120: | |
503 | if (NULL != ctrl.message) | |
504 | debugs(9, DBG_IMPORTANT, "FTP server is busy: " << ctrl.message->key); | |
5517260a | 505 | forwardPreliminaryReply(&Ftp::Relay::scheduleReadControlReply); |
434a79b0 DK |
506 | break; |
507 | default: | |
508 | failed(); | |
509 | break; | |
510 | } | |
511 | } | |
512 | ||
513 | void | |
5517260a | 514 | Ftp::Relay::sendCommand() |
434a79b0 | 515 | { |
789217a2 | 516 | if (!fwd->request->header.has(Http::HdrType::FTP_COMMAND)) { |
92cfc72f | 517 | abortAll("Internal error: FTP relay request with no command"); |
434a79b0 DK |
518 | return; |
519 | } | |
520 | ||
c8059ea9 | 521 | HttpHeader &header = fwd->request->header; |
789217a2 FC |
522 | assert(header.has(Http::HdrType::FTP_COMMAND)); |
523 | const String &cmd = header.findEntry(Http::HdrType::FTP_COMMAND)->value; | |
524 | assert(header.has(Http::HdrType::FTP_ARGUMENTS)); | |
525 | const String ¶ms = header.findEntry(Http::HdrType::FTP_ARGUMENTS)->value; | |
c8059ea9 DK |
526 | |
527 | if (params.size() > 0) | |
e7ce227f | 528 | debugs(9, 5, "command: " << cmd << ", parameters: " << params); |
434a79b0 | 529 | else |
e7ce227f | 530 | debugs(9, 5, "command: " << cmd << ", no parameters"); |
434a79b0 | 531 | |
e7ce227f | 532 | if (serverState() == fssHandlePasv || |
27c841f6 AR |
533 | serverState() == fssHandleEpsv || |
534 | serverState() == fssHandleEprt || | |
535 | serverState() == fssHandlePort) { | |
000e664b AR |
536 | sendPassive(); |
537 | return; | |
538 | } | |
539 | ||
1ab04517 | 540 | SBuf buf; |
c8059ea9 | 541 | if (params.size() > 0) |
1ab04517 | 542 | buf.Printf("%s %s%s", cmd.termedBuf(), params.termedBuf(), Ftp::crlf); |
434a79b0 | 543 | else |
1ab04517 | 544 | buf.Printf("%s%s", cmd.termedBuf(), Ftp::crlf); |
434a79b0 | 545 | |
1ab04517 | 546 | writeCommand(buf.c_str()); |
434a79b0 | 547 | |
cff221ee | 548 | state = |
e7ce227f AR |
549 | serverState() == fssHandleCdup ? SENT_CDUP : |
550 | serverState() == fssHandleCwd ? SENT_CWD : | |
551 | serverState() == fssHandleFeat ? SENT_FEAT : | |
552 | serverState() == fssHandleDataRequest ? SENT_DATA_REQUEST : | |
553 | serverState() == fssHandleUploadRequest ? SENT_DATA_REQUEST : | |
554 | serverState() == fssConnected ? SENT_USER : | |
555 | serverState() == fssHandlePass ? SENT_PASS : | |
434a79b0 | 556 | SENT_COMMAND; |
3238b9b6 CT |
557 | |
558 | if (state == SENT_DATA_REQUEST) { | |
559 | CbcPointer<ConnStateData> &mgr = fwd->request->clientConnectionManager; | |
560 | if (mgr.valid()) { | |
561 | if (Ftp::Server *srv = dynamic_cast<Ftp::Server*>(mgr.get())) { | |
562 | typedef NullaryMemFunT<Ftp::Server> CbDialer; | |
563 | AsyncCall::Pointer call = JobCallback(11, 3, CbDialer, srv, | |
564 | Ftp::Server::startWaitingForOrigin); | |
565 | ScheduleCallHere(call); | |
566 | originWaitInProgress = true; | |
567 | } | |
568 | } | |
569 | } | |
434a79b0 DK |
570 | } |
571 | ||
572 | void | |
5517260a | 573 | Ftp::Relay::readReply() |
434a79b0 | 574 | { |
e7ce227f AR |
575 | assert(serverState() == fssConnected || |
576 | serverState() == fssHandleUploadRequest); | |
434a79b0 | 577 | |
4f1c93a7 | 578 | if (Is1xx(ctrl.replycode)) |
5517260a | 579 | forwardPreliminaryReply(&Ftp::Relay::scheduleReadControlReply); |
434a79b0 DK |
580 | else |
581 | forwardReply(); | |
582 | } | |
583 | ||
5f3898e2 | 584 | void |
5517260a | 585 | Ftp::Relay::readFeatReply() |
5f3898e2 | 586 | { |
e7ce227f | 587 | assert(serverState() == fssHandleFeat); |
5f3898e2 | 588 | |
4f1c93a7 | 589 | if (Is1xx(ctrl.replycode)) |
5f3898e2 AR |
590 | return; // ignore preliminary replies |
591 | ||
592 | forwardReply(); | |
593 | } | |
594 | ||
434a79b0 | 595 | void |
5517260a | 596 | Ftp::Relay::readPasvReply() |
434a79b0 | 597 | { |
e7ce227f | 598 | assert(serverState() == fssHandlePasv || serverState() == fssHandleEpsv || serverState() == fssHandlePort || serverState() == fssHandleEprt); |
434a79b0 | 599 | |
4f1c93a7 | 600 | if (Is1xx(ctrl.replycode)) |
434a79b0 DK |
601 | return; // ignore preliminary replies |
602 | ||
92ae4c86 | 603 | if (handlePasvReply(updateMaster().clientDataAddr)) |
434a79b0 | 604 | forwardReply(); |
73950ceb | 605 | else |
434a79b0 DK |
606 | forwardError(); |
607 | } | |
608 | ||
cff221ee | 609 | void |
5517260a | 610 | Ftp::Relay::readEpsvReply() |
cff221ee | 611 | { |
4f1c93a7 | 612 | if (Is1xx(ctrl.replycode)) |
cff221ee AR |
613 | return; // ignore preliminary replies |
614 | ||
92ae4c86 | 615 | if (handleEpsvReply(updateMaster().clientDataAddr)) { |
000e664b AR |
616 | if (ctrl.message == NULL) |
617 | return; // didn't get complete reply yet | |
618 | ||
cff221ee | 619 | forwardReply(); |
000e664b | 620 | } else |
cff221ee AR |
621 | forwardError(); |
622 | } | |
623 | ||
434a79b0 | 624 | void |
5517260a | 625 | Ftp::Relay::readDataReply() |
434a79b0 | 626 | { |
e7ce227f AR |
627 | assert(serverState() == fssHandleDataRequest || |
628 | serverState() == fssHandleUploadRequest); | |
a5d444a5 | 629 | |
6af7d28d | 630 | if (ctrl.replycode == 125 || ctrl.replycode == 150) { |
e7ce227f | 631 | if (serverState() == fssHandleDataRequest) |
5517260a | 632 | forwardPreliminaryReply(&Ftp::Relay::startDataDownload); |
ec69bdb2 CT |
633 | else if (fwd->request->forcedBodyContinuation /*&& serverState() == fssHandleUploadRequest*/) |
634 | startDataUpload(); | |
f53969cc | 635 | else // serverState() == fssHandleUploadRequest |
5517260a | 636 | forwardPreliminaryReply(&Ftp::Relay::startDataUpload); |
a5d444a5 | 637 | } else |
434a79b0 | 638 | forwardReply(); |
54a6c0cd CT |
639 | } |
640 | ||
641 | bool | |
5517260a | 642 | Ftp::Relay::startDirTracking() |
54a6c0cd CT |
643 | { |
644 | if (!fwd->request->clientConnectionManager->port->ftp_track_dirs) | |
645 | return false; | |
646 | ||
e7ce227f | 647 | debugs(9, 5, "start directory tracking"); |
54a6c0cd CT |
648 | savedReply.message = ctrl.message; |
649 | savedReply.lastCommand = ctrl.last_command; | |
650 | savedReply.lastReply = ctrl.last_reply; | |
651 | savedReply.replyCode = ctrl.replycode; | |
652 | ||
653 | ctrl.last_command = NULL; | |
654 | ctrl.last_reply = NULL; | |
655 | ctrl.message = NULL; | |
656 | ctrl.offset = 0; | |
657 | writeCommand("PWD\r\n"); | |
658 | return true; | |
659 | } | |
660 | ||
661 | void | |
5517260a | 662 | Ftp::Relay::stopDirTracking() |
54a6c0cd | 663 | { |
e7ce227f | 664 | debugs(9, 5, "got code from pwd: " << ctrl.replycode << ", msg: " << ctrl.last_reply); |
54a6c0cd | 665 | |
92ae4c86 AR |
666 | if (ctrl.replycode == 257) |
667 | updateMaster().workingDir = Ftp::UnescapeDoubleQuoted(ctrl.last_reply); | |
54a6c0cd CT |
668 | |
669 | wordlistDestroy(&ctrl.message); | |
670 | safe_free(ctrl.last_command); | |
671 | safe_free(ctrl.last_reply); | |
672 | ||
673 | ctrl.message = savedReply.message; | |
674 | ctrl.last_command = savedReply.lastCommand; | |
675 | ctrl.last_reply = savedReply.lastReply; | |
676 | ctrl.replycode = savedReply.replyCode; | |
677 | ||
678 | savedReply.message = NULL; | |
679 | savedReply.lastReply = NULL; | |
680 | savedReply.lastCommand = NULL; | |
681 | } | |
682 | ||
683 | void | |
5517260a | 684 | Ftp::Relay::readCwdOrCdupReply() |
54a6c0cd | 685 | { |
e7ce227f AR |
686 | assert(serverState() == fssHandleCwd || |
687 | serverState() == fssHandleCdup); | |
54a6c0cd | 688 | |
e7ce227f | 689 | debugs(9, 5, "got code " << ctrl.replycode << ", msg: " << ctrl.last_reply); |
54a6c0cd | 690 | |
4f1c93a7 | 691 | if (Is1xx(ctrl.replycode)) |
54a6c0cd CT |
692 | return; |
693 | ||
694 | if (weAreTrackingDir()) { // we are tracking | |
695 | stopDirTracking(); // and forward the delayed response below | |
696 | } else if (startDirTracking()) | |
697 | return; | |
698 | ||
699 | forwardReply(); | |
700 | } | |
701 | ||
702 | void | |
5517260a | 703 | Ftp::Relay::readUserOrPassReply() |
54a6c0cd | 704 | { |
4f1c93a7 | 705 | if (Is1xx(ctrl.replycode)) |
54a6c0cd CT |
706 | return; //Just ignore |
707 | ||
708 | if (weAreTrackingDir()) { // we are tracking | |
709 | stopDirTracking(); // and forward the delayed response below | |
710 | } else if (ctrl.replycode == 230) { // successful login | |
711 | if (startDirTracking()) | |
712 | return; | |
713 | } | |
714 | ||
715 | forwardReply(); | |
434a79b0 DK |
716 | } |
717 | ||
718 | void | |
5517260a | 719 | Ftp::Relay::readTransferDoneReply() |
434a79b0 | 720 | { |
e7ce227f | 721 | debugs(9, 3, status()); |
434a79b0 | 722 | |
ba3fe8d9 EB |
723 | // RFC 959 says that code 226 may indicate a successful response to a file |
724 | // transfer and file abort commands, but since we do not send abort | |
725 | // commands, let's assume it was a successful file transfer. | |
726 | if (ctrl.replycode == 226 || ctrl.replycode == 250) { | |
727 | markParsedVirginReplyAsWhole("Ftp::Relay::readTransferDoneReply() code 226 or 250"); | |
728 | } else { | |
e7ce227f AR |
729 | debugs(9, DBG_IMPORTANT, "got FTP code " << ctrl.replycode << |
730 | " after reading response data"); | |
434a79b0 DK |
731 | } |
732 | ||
3238b9b6 CT |
733 | debugs(9, 2, "Complete data downloading"); |
734 | ||
735 | serverComplete(); | |
434a79b0 DK |
736 | } |
737 | ||
738 | void | |
43446566 | 739 | Ftp::Relay::dataChannelConnected(const CommConnectCbParams &io) |
434a79b0 | 740 | { |
e7ce227f | 741 | debugs(9, 3, status()); |
2b6b1bcb | 742 | dataConnWait.finish(); |
434a79b0 | 743 | |
43446566 | 744 | if (io.flag != Comm::OK) { |
e7ce227f | 745 | debugs(9, 2, "failed to connect FTP server data channel"); |
43446566 | 746 | forwardError(ERR_CONNECT_FAIL, io.xerrno); |
434a79b0 DK |
747 | return; |
748 | } | |
749 | ||
43446566 | 750 | debugs(9, 2, "connected FTP server data channel: " << io.conn); |
434a79b0 | 751 | |
43446566 | 752 | data.opened(io.conn, dataCloser()); |
434a79b0 DK |
753 | |
754 | sendCommand(); | |
755 | } | |
756 | ||
757 | void | |
5517260a | 758 | Ftp::Relay::scheduleReadControlReply() |
434a79b0 | 759 | { |
5517260a | 760 | Ftp::Client::scheduleReadControlReply(0); |
434a79b0 DK |
761 | } |
762 | ||
92cfc72f CT |
763 | bool |
764 | Ftp::Relay::abortOnData(const char *reason) | |
765 | { | |
766 | debugs(9, 3, "aborting transaction for " << reason << | |
767 | "; FD " << (ctrl.conn != NULL ? ctrl.conn->fd : -1) << ", Data FD " << (data.conn != NULL ? data.conn->fd : -1) << ", this " << this); | |
768 | // this method is only called to handle data connection problems | |
769 | // the control connection should keep going | |
770 | ||
771 | #if USE_ADAPTATION | |
772 | if (adaptedBodySource != NULL) | |
773 | stopConsumingFrom(adaptedBodySource); | |
774 | #endif | |
775 | ||
776 | if (Comm::IsConnOpen(data.conn)) | |
777 | dataComplete(); | |
778 | ||
779 | return !Comm::IsConnOpen(ctrl.conn); | |
780 | } | |
781 | ||
3238b9b6 CT |
782 | void |
783 | Ftp::Relay::stopOriginWait(int code) | |
784 | { | |
785 | if (originWaitInProgress) { | |
786 | CbcPointer<ConnStateData> &mgr = fwd->request->clientConnectionManager; | |
787 | if (mgr.valid()) { | |
788 | if (Ftp::Server *srv = dynamic_cast<Ftp::Server*>(mgr.get())) { | |
789 | typedef UnaryMemFunT<Ftp::Server, int> CbDialer; | |
790 | AsyncCall::Pointer call = asyncCall(11, 3, "Ftp::Server::stopWaitingForOrigin", | |
791 | CbDialer(srv, &Ftp::Server::stopWaitingForOrigin, code)); | |
792 | ScheduleCallHere(call); | |
793 | } | |
794 | } | |
795 | originWaitInProgress = false; | |
796 | } | |
797 | } | |
798 | ||
92cfc72f | 799 | void |
7e9f330d | 800 | Ftp::Relay::HandleStoreAbort(Relay *ftpClient) |
92cfc72f | 801 | { |
92cfc72f | 802 | debugs(9, 2, "Client Data connection closed!"); |
92cfc72f CT |
803 | if (Comm::IsConnOpen(ftpClient->data.conn)) |
804 | ftpClient->dataComplete(); | |
805 | } | |
806 | ||
2b6b1bcb | 807 | void |
5517260a | 808 | Ftp::StartRelay(FwdState *const fwdState) |
434a79b0 | 809 | { |
2b6b1bcb | 810 | AsyncJob::Start(new Ftp::Relay(fwdState)); |
434a79b0 | 811 | } |
f53969cc | 812 |