]>
Commit | Line | Data |
---|---|---|
434a79b0 | 1 | /* |
ef57eb7b | 2 | * Copyright (C) 1996-2016 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" |
434a79b0 DK |
18 | #include "HttpHdrCc.h" |
19 | #include "HttpRequest.h" | |
1ab04517 | 20 | #include "SBuf.h" |
27c841f6 | 21 | #include "servers/FtpServer.h" |
434a79b0 DK |
22 | #include "SquidTime.h" |
23 | #include "Store.h" | |
434a79b0 DK |
24 | #include "wordlist.h" |
25 | ||
27c841f6 AR |
26 | namespace Ftp |
27 | { | |
434a79b0 | 28 | |
e7ce227f | 29 | /// An FTP client receiving native FTP commands from our FTP server |
5517260a AR |
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 | |
434a79b0 | 33 | { |
5c2f68b7 AJ |
34 | CBDATA_CLASS(Relay); |
35 | ||
434a79b0 | 36 | public: |
5517260a AR |
37 | explicit Relay(FwdState *const fwdState); |
38 | virtual ~Relay(); | |
434a79b0 DK |
39 | |
40 | protected: | |
92ae4c86 AR |
41 | const Ftp::MasterState &master() const; |
42 | Ftp::MasterState &updateMaster(); | |
e7ce227f AR |
43 | Ftp::ServerState serverState() const { return master().serverState; } |
44 | void serverState(const Ftp::ServerState newState); | |
92ae4c86 | 45 | |
5517260a | 46 | /* Ftp::Client API */ |
434a79b0 | 47 | virtual void failed(err_type error = ERR_NONE, int xerrno = 0); |
43446566 | 48 | virtual void dataChannelConnected(const CommConnectCbParams &io); |
5517260a | 49 | |
fccd4a86 | 50 | /* Client API */ |
5517260a | 51 | virtual void serverComplete(); |
434a79b0 | 52 | virtual void handleControlReply(); |
5517260a | 53 | virtual void processReplyBody(); |
434a79b0 | 54 | virtual void handleRequestBodyProducerAborted(); |
719c885c AR |
55 | virtual bool mayReadVirginReplyBody() const; |
56 | virtual void completeForwarding(); | |
5517260a AR |
57 | |
58 | /* AsyncJob API */ | |
59 | virtual void start(); | |
60 | ||
434a79b0 DK |
61 | void forwardReply(); |
62 | void forwardError(err_type error = ERR_NONE, int xerrno = 0); | |
719c885c | 63 | void failedErrorMessage(err_type error, int xerrno); |
43446566 | 64 | HttpReply *createHttpReply(const Http::StatusCode httpStatus, const int64_t clen = 0); |
434a79b0 | 65 | void handleDataRequest(); |
a5d444a5 DK |
66 | void startDataDownload(); |
67 | void startDataUpload(); | |
54a6c0cd CT |
68 | bool startDirTracking(); |
69 | void stopDirTracking(); | |
70 | bool weAreTrackingDir() const {return savedReply.message != NULL;} | |
434a79b0 | 71 | |
5517260a | 72 | typedef void (Relay::*PreliminaryCb)(); |
434a79b0 DK |
73 | void forwardPreliminaryReply(const PreliminaryCb cb); |
74 | void proceedAfterPreliminaryReply(); | |
75 | PreliminaryCb thePreliminaryCb; | |
76 | ||
5517260a | 77 | typedef void (Relay::*SM_FUNC)(); |
434a79b0 | 78 | static const SM_FUNC SM_FUNCS[]; |
125894ba | 79 | void readGreeting(); |
434a79b0 DK |
80 | void sendCommand(); |
81 | void readReply(); | |
5f3898e2 | 82 | void readFeatReply(); |
434a79b0 DK |
83 | void readPasvReply(); |
84 | void readDataReply(); | |
85 | void readTransferDoneReply(); | |
000e664b | 86 | void readEpsvReply(); |
54a6c0cd CT |
87 | void readCwdOrCdupReply(); |
88 | void readUserOrPassReply(); | |
434a79b0 | 89 | |
434a79b0 DK |
90 | void scheduleReadControlReply(); |
91 | ||
719c885c AR |
92 | bool forwardingCompleted; ///< completeForwarding() has been called |
93 | ||
54a6c0cd CT |
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 | |
434a79b0 DK |
100 | }; |
101 | ||
5517260a | 102 | } // namespace Ftp |
434a79b0 | 103 | |
5517260a AR |
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 | |
e7ce227f AR |
110 | NULL,/* &Ftp::Relay::readReply */ // SENT_TYPE |
111 | NULL,/* &Ftp::Relay::readReply */ // SENT_MDTM | |
112 | NULL,/* &Ftp::Relay::readReply */ // SENT_SIZE | |
000e664b AR |
113 | NULL, // SENT_EPRT |
114 | NULL, // SENT_PORT | |
5517260a AR |
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 | |
e7ce227f AR |
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 | |
5517260a AR |
126 | &Ftp::Relay::readTransferDoneReply, // READING_DATA |
127 | &Ftp::Relay::readReply, // WRITING_DATA | |
e7ce227f | 128 | NULL,/* &Ftp::Relay::readReply */ // SENT_MKDIR |
5517260a | 129 | &Ftp::Relay::readFeatReply, // SENT_FEAT |
e7ce227f | 130 | NULL,/* &Ftp::Relay::readPwdReply */ // SENT_PWD |
5517260a AR |
131 | &Ftp::Relay::readCwdOrCdupReply, // SENT_CDUP |
132 | &Ftp::Relay::readDataReply,// SENT_DATA_REQUEST | |
133 | &Ftp::Relay::readReply, // SENT_COMMAND | |
000e664b | 134 | NULL |
434a79b0 DK |
135 | }; |
136 | ||
5517260a | 137 | Ftp::Relay::Relay(FwdState *const fwdState): |
f53969cc SM |
138 | AsyncJob("Ftp::Relay"), |
139 | Ftp::Client(fwdState), | |
140 | thePreliminaryCb(NULL), | |
141 | forwardingCompleted(false) | |
434a79b0 | 142 | { |
a5e978fd | 143 | savedReply.message = NULL; |
54a6c0cd CT |
144 | savedReply.lastCommand = NULL; |
145 | savedReply.lastReply = NULL; | |
146 | savedReply.replyCode = 0; | |
147 | ||
a8f874af AR |
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(); | |
434a79b0 DK |
151 | } |
152 | ||
5517260a | 153 | Ftp::Relay::~Relay() |
434a79b0 | 154 | { |
fccd4a86 | 155 | closeServer(); // TODO: move to clients/Client.cc? |
54a6c0cd CT |
156 | if (savedReply.message) |
157 | wordlistDestroy(&savedReply.message); | |
158 | ||
159 | xfree(savedReply.lastCommand); | |
160 | xfree(savedReply.lastReply); | |
434a79b0 DK |
161 | } |
162 | ||
163 | void | |
5517260a | 164 | Ftp::Relay::start() |
434a79b0 | 165 | { |
92ae4c86 | 166 | if (!master().clientReadGreeting) |
5517260a | 167 | Ftp::Client::start(); |
27c841f6 AR |
168 | else if (serverState() == fssHandleDataRequest || |
169 | serverState() == fssHandleUploadRequest) | |
434a79b0 DK |
170 | handleDataRequest(); |
171 | else | |
172 | sendCommand(); | |
173 | } | |
174 | ||
29f23abf AR |
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 | |
5517260a | 178 | Ftp::Relay::serverComplete() |
29f23abf | 179 | { |
89b1d7a2 AR |
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 | } | |
29f23abf | 196 | } |
5517260a | 197 | Ftp::Client::serverComplete(); |
29f23abf AR |
198 | } |
199 | ||
e7ce227f AR |
200 | /// Safely returns the master state, |
201 | /// with safety checks in case the Ftp::Server side of the master xact is gone. | |
92ae4c86 | 202 | Ftp::MasterState & |
5517260a | 203 | Ftp::Relay::updateMaster() |
92ae4c86 AR |
204 | { |
205 | CbcPointer<ConnStateData> &mgr = fwd->request->clientConnectionManager; | |
206 | if (mgr.valid()) { | |
207 | if (Ftp::Server *srv = dynamic_cast<Ftp::Server*>(mgr.get())) | |
aea65fec | 208 | return *srv->master; |
92ae4c86 AR |
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 | ||
e7ce227f | 217 | /// A const variant of updateMaster(). |
92ae4c86 | 218 | const Ftp::MasterState & |
5517260a | 219 | Ftp::Relay::master() const |
92ae4c86 | 220 | { |
e7ce227f | 221 | return const_cast<Ftp::Relay*>(this)->updateMaster(); // avoid code dupe |
434a79b0 DK |
222 | } |
223 | ||
e7ce227f | 224 | /// Changes server state and debugs about that important event. |
434a79b0 | 225 | void |
e7ce227f | 226 | Ftp::Relay::serverState(const Ftp::ServerState newState) |
434a79b0 | 227 | { |
92ae4c86 | 228 | Ftp::ServerState &cltState = updateMaster().serverState; |
719c885c AR |
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. | |
e7ce227f | 235 | * We complete forwarding when the response adaptation is over |
719c885c AR |
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 | |
5517260a | 242 | Ftp::Relay::completeForwarding() |
719c885c AR |
243 | { |
244 | debugs(9, 5, forwardingCompleted); | |
245 | if (forwardingCompleted) | |
246 | return; | |
247 | forwardingCompleted = true; | |
5517260a | 248 | Ftp::Client::completeForwarding(); |
434a79b0 DK |
249 | } |
250 | ||
251 | void | |
5517260a | 252 | Ftp::Relay::failed(err_type error, int xerrno) |
434a79b0 DK |
253 | { |
254 | if (!doneWithServer()) | |
e7ce227f | 255 | serverState(fssError); |
434a79b0 | 256 | |
719c885c AR |
257 | // TODO: we need to customize ErrorState instead |
258 | if (entry->isEmpty()) | |
259 | failedErrorMessage(error, xerrno); // as a reply | |
260 | ||
5517260a | 261 | Ftp::Client::failed(error, xerrno); |
434a79b0 DK |
262 | } |
263 | ||
264 | void | |
5517260a | 265 | Ftp::Relay::failedErrorMessage(err_type error, int xerrno) |
434a79b0 DK |
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 | |
5517260a | 275 | Ftp::Relay::processReplyBody() |
434a79b0 | 276 | { |
e7ce227f | 277 | debugs(9, 3, status()); |
434a79b0 DK |
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) { | |
e7ce227f | 291 | debugs(9, 3, "returning due to adaptationAccessCheckPending"); |
434a79b0 DK |
292 | return; |
293 | } | |
294 | ||
295 | #endif | |
296 | ||
adaff124 DK |
297 | if (data.readBuf != NULL && data.readBuf->hasContent()) { |
298 | const mb_size_t csize = data.readBuf->contentSize(); | |
e7ce227f | 299 | debugs(9, 5, "writing " << csize << " bytes to the reply"); |
434a79b0 DK |
300 | addVirginReplyBody(data.readBuf->content(), csize); |
301 | data.readBuf->consume(csize); | |
302 | } | |
303 | ||
304 | entry->flush(); | |
305 | ||
306 | maybeReadVirginBody(); | |
307 | } | |
308 | ||
309 | void | |
5517260a | 310 | Ftp::Relay::handleControlReply() |
434a79b0 | 311 | { |
54a6c0cd CT |
312 | if (!request->clientConnectionManager.valid()) { |
313 | debugs(9, 5, "client connection gone"); | |
314 | closeServer(); | |
315 | return; | |
316 | } | |
317 | ||
5517260a | 318 | Ftp::Client::handleControlReply(); |
434a79b0 DK |
319 | if (ctrl.message == NULL) |
320 | return; // didn't get complete reply yet | |
321 | ||
29f23abf | 322 | assert(state < END); |
000e664b | 323 | assert(this->SM_FUNCS[state] != NULL); |
434a79b0 DK |
324 | (this->*SM_FUNCS[state])(); |
325 | } | |
326 | ||
327 | void | |
5517260a | 328 | Ftp::Relay::handleRequestBodyProducerAborted() |
434a79b0 | 329 | { |
fccd4a86 | 330 | ::Client::handleRequestBodyProducerAborted(); |
434a79b0 | 331 | |
a5d444a5 | 332 | failed(ERR_READ_ERROR); |
434a79b0 DK |
333 | } |
334 | ||
719c885c | 335 | bool |
5517260a | 336 | Ftp::Relay::mayReadVirginReplyBody() const |
719c885c AR |
337 | { |
338 | // TODO: move this method to the regular FTP server? | |
339 | return Comm::IsConnOpen(data.conn); | |
340 | } | |
341 | ||
434a79b0 | 342 | void |
5517260a | 343 | Ftp::Relay::forwardReply() |
434a79b0 DK |
344 | { |
345 | assert(entry->isEmpty()); | |
346 | EBIT_CLR(entry->flags, ENTRY_FWD_HDR_WAIT); | |
347 | ||
15c82b83 | 348 | HttpReply *const reply = createHttpReply(Http::scNoContent); |
88df846b | 349 | reply->sources |= HttpMsg::srcFtp; |
434a79b0 DK |
350 | |
351 | setVirginReply(reply); | |
352 | adaptOrFinalizeReply(); | |
353 | ||
434a79b0 DK |
354 | serverComplete(); |
355 | } | |
356 | ||
357 | void | |
5517260a | 358 | Ftp::Relay::forwardPreliminaryReply(const PreliminaryCb cb) |
434a79b0 | 359 | { |
e7ce227f | 360 | debugs(9, 5, "forwarding preliminary reply to client"); |
434a79b0 | 361 | |
92ae4c86 AR |
362 | // we must prevent concurrent ConnStateData::sendControlMsg() calls |
363 | Must(thePreliminaryCb == NULL); | |
434a79b0 DK |
364 | thePreliminaryCb = cb; |
365 | ||
366 | const HttpReply::Pointer reply = createHttpReply(Http::scContinue); | |
367 | ||
368 | // the Sink will use this to call us back after writing 1xx to the client | |
5517260a | 369 | typedef NullaryMemFunT<Relay> CbDialer; |
434a79b0 | 370 | const AsyncCall::Pointer call = JobCallback(11, 3, CbDialer, this, |
27c841f6 | 371 | Ftp::Relay::proceedAfterPreliminaryReply); |
434a79b0 DK |
372 | |
373 | CallJobHere1(9, 4, request->clientConnectionManager, ConnStateData, | |
374 | ConnStateData::sendControlMsg, HttpControlMsg(reply, call)); | |
375 | } | |
376 | ||
377 | void | |
5517260a | 378 | Ftp::Relay::proceedAfterPreliminaryReply() |
434a79b0 | 379 | { |
e7ce227f | 380 | debugs(9, 5, "proceeding after preliminary reply to client"); |
434a79b0 | 381 | |
92ae4c86 | 382 | Must(thePreliminaryCb != NULL); |
434a79b0 DK |
383 | const PreliminaryCb cb = thePreliminaryCb; |
384 | thePreliminaryCb = NULL; | |
385 | (this->*cb)(); | |
386 | } | |
387 | ||
388 | void | |
5517260a | 389 | Ftp::Relay::forwardError(err_type error, int xerrno) |
434a79b0 | 390 | { |
434a79b0 DK |
391 | failed(error, xerrno); |
392 | } | |
393 | ||
394 | HttpReply * | |
43446566 AR |
395 | Ftp::Relay::createHttpReply(const Http::StatusCode httpStatus, const int64_t clen) |
396 | { | |
397 | HttpReply *const reply = Ftp::HttpReplyWrapper(ctrl.replycode, ctrl.last_reply, httpStatus, clen); | |
a2c7f09a AR |
398 | if (ctrl.message) { |
399 | for (wordlist *W = ctrl.message; W && W->next; W = W->next) | |
789217a2 FC |
400 | reply->header.putStr(Http::HdrType::FTP_PRE, httpHeaderQuoteString(W->key).c_str()); |
401 | // no hdrCacheInit() is needed for after Http::HdrType::FTP_PRE addition | |
a2c7f09a | 402 | } |
434a79b0 DK |
403 | return reply; |
404 | } | |
405 | ||
406 | void | |
5517260a | 407 | Ftp::Relay::handleDataRequest() |
434a79b0 | 408 | { |
92ae4c86 | 409 | data.addr(master().clientDataAddr); |
434a79b0 DK |
410 | connectDataChannel(); |
411 | } | |
412 | ||
413 | void | |
5517260a | 414 | Ftp::Relay::startDataDownload() |
434a79b0 DK |
415 | { |
416 | assert(Comm::IsConnOpen(data.conn)); | |
417 | ||
e7ce227f | 418 | debugs(9, 3, "begin data transfer from " << data.conn->remote << |
434a79b0 DK |
419 | " (" << data.conn->local << ")"); |
420 | ||
421 | HttpReply *const reply = createHttpReply(Http::scOkay, -1); | |
88df846b CT |
422 | reply->sources |= HttpMsg::srcFtp; |
423 | ||
434a79b0 DK |
424 | EBIT_CLR(entry->flags, ENTRY_FWD_HDR_WAIT); |
425 | setVirginReply(reply); | |
426 | adaptOrFinalizeReply(); | |
427 | ||
434a79b0 DK |
428 | maybeReadVirginBody(); |
429 | state = READING_DATA; | |
430 | } | |
431 | ||
a5d444a5 | 432 | void |
5517260a | 433 | Ftp::Relay::startDataUpload() |
a5d444a5 DK |
434 | { |
435 | assert(Comm::IsConnOpen(data.conn)); | |
436 | ||
e7ce227f | 437 | debugs(9, 3, "begin data transfer to " << data.conn->remote << |
a5d444a5 DK |
438 | " (" << data.conn->local << ")"); |
439 | ||
440 | if (!startRequestBodyFlow()) { // register to receive body data | |
441 | failed(); | |
442 | return; | |
443 | } | |
444 | ||
000e664b | 445 | state = WRITING_DATA; |
a5d444a5 DK |
446 | } |
447 | ||
434a79b0 | 448 | void |
5517260a | 449 | Ftp::Relay::readGreeting() |
434a79b0 | 450 | { |
92ae4c86 | 451 | assert(!master().clientReadGreeting); |
434a79b0 DK |
452 | |
453 | switch (ctrl.replycode) { | |
454 | case 220: | |
92ae4c86 | 455 | updateMaster().clientReadGreeting = true; |
e7ce227f AR |
456 | if (serverState() == fssBegin) |
457 | serverState(fssConnected); | |
29268829 | 458 | |
43446566 AR |
459 | // Do not forward server greeting to the user because our FTP Server |
460 | // has greeted the user already. Also, an original origin greeting may | |
461 | // confuse a user that has changed the origin mid-air. | |
29268829 DK |
462 | |
463 | start(); | |
434a79b0 DK |
464 | break; |
465 | case 120: | |
466 | if (NULL != ctrl.message) | |
467 | debugs(9, DBG_IMPORTANT, "FTP server is busy: " << ctrl.message->key); | |
5517260a | 468 | forwardPreliminaryReply(&Ftp::Relay::scheduleReadControlReply); |
434a79b0 DK |
469 | break; |
470 | default: | |
471 | failed(); | |
472 | break; | |
473 | } | |
474 | } | |
475 | ||
476 | void | |
5517260a | 477 | Ftp::Relay::sendCommand() |
434a79b0 | 478 | { |
789217a2 | 479 | if (!fwd->request->header.has(Http::HdrType::FTP_COMMAND)) { |
43446566 | 480 | abortTransaction("Internal error: FTP relay request with no command"); |
434a79b0 DK |
481 | return; |
482 | } | |
483 | ||
c8059ea9 | 484 | HttpHeader &header = fwd->request->header; |
789217a2 FC |
485 | assert(header.has(Http::HdrType::FTP_COMMAND)); |
486 | const String &cmd = header.findEntry(Http::HdrType::FTP_COMMAND)->value; | |
487 | assert(header.has(Http::HdrType::FTP_ARGUMENTS)); | |
488 | const String ¶ms = header.findEntry(Http::HdrType::FTP_ARGUMENTS)->value; | |
c8059ea9 DK |
489 | |
490 | if (params.size() > 0) | |
e7ce227f | 491 | debugs(9, 5, "command: " << cmd << ", parameters: " << params); |
434a79b0 | 492 | else |
e7ce227f | 493 | debugs(9, 5, "command: " << cmd << ", no parameters"); |
434a79b0 | 494 | |
e7ce227f | 495 | if (serverState() == fssHandlePasv || |
27c841f6 AR |
496 | serverState() == fssHandleEpsv || |
497 | serverState() == fssHandleEprt || | |
498 | serverState() == fssHandlePort) { | |
000e664b AR |
499 | sendPassive(); |
500 | return; | |
501 | } | |
502 | ||
1ab04517 | 503 | SBuf buf; |
c8059ea9 | 504 | if (params.size() > 0) |
1ab04517 | 505 | buf.Printf("%s %s%s", cmd.termedBuf(), params.termedBuf(), Ftp::crlf); |
434a79b0 | 506 | else |
1ab04517 | 507 | buf.Printf("%s%s", cmd.termedBuf(), Ftp::crlf); |
434a79b0 | 508 | |
1ab04517 | 509 | writeCommand(buf.c_str()); |
434a79b0 | 510 | |
cff221ee | 511 | state = |
e7ce227f AR |
512 | serverState() == fssHandleCdup ? SENT_CDUP : |
513 | serverState() == fssHandleCwd ? SENT_CWD : | |
514 | serverState() == fssHandleFeat ? SENT_FEAT : | |
515 | serverState() == fssHandleDataRequest ? SENT_DATA_REQUEST : | |
516 | serverState() == fssHandleUploadRequest ? SENT_DATA_REQUEST : | |
517 | serverState() == fssConnected ? SENT_USER : | |
518 | serverState() == fssHandlePass ? SENT_PASS : | |
434a79b0 DK |
519 | SENT_COMMAND; |
520 | } | |
521 | ||
522 | void | |
5517260a | 523 | Ftp::Relay::readReply() |
434a79b0 | 524 | { |
e7ce227f AR |
525 | assert(serverState() == fssConnected || |
526 | serverState() == fssHandleUploadRequest); | |
434a79b0 DK |
527 | |
528 | if (100 <= ctrl.replycode && ctrl.replycode < 200) | |
5517260a | 529 | forwardPreliminaryReply(&Ftp::Relay::scheduleReadControlReply); |
434a79b0 DK |
530 | else |
531 | forwardReply(); | |
532 | } | |
533 | ||
5f3898e2 | 534 | void |
5517260a | 535 | Ftp::Relay::readFeatReply() |
5f3898e2 | 536 | { |
e7ce227f | 537 | assert(serverState() == fssHandleFeat); |
5f3898e2 AR |
538 | |
539 | if (100 <= ctrl.replycode && ctrl.replycode < 200) | |
540 | return; // ignore preliminary replies | |
541 | ||
542 | forwardReply(); | |
543 | } | |
544 | ||
434a79b0 | 545 | void |
5517260a | 546 | Ftp::Relay::readPasvReply() |
434a79b0 | 547 | { |
e7ce227f | 548 | assert(serverState() == fssHandlePasv || serverState() == fssHandleEpsv || serverState() == fssHandlePort || serverState() == fssHandleEprt); |
434a79b0 DK |
549 | |
550 | if (100 <= ctrl.replycode && ctrl.replycode < 200) | |
551 | return; // ignore preliminary replies | |
552 | ||
92ae4c86 | 553 | if (handlePasvReply(updateMaster().clientDataAddr)) |
434a79b0 | 554 | forwardReply(); |
73950ceb | 555 | else |
434a79b0 DK |
556 | forwardError(); |
557 | } | |
558 | ||
cff221ee | 559 | void |
5517260a | 560 | Ftp::Relay::readEpsvReply() |
cff221ee | 561 | { |
cff221ee AR |
562 | if (100 <= ctrl.replycode && ctrl.replycode < 200) |
563 | return; // ignore preliminary replies | |
564 | ||
92ae4c86 | 565 | if (handleEpsvReply(updateMaster().clientDataAddr)) { |
000e664b AR |
566 | if (ctrl.message == NULL) |
567 | return; // didn't get complete reply yet | |
568 | ||
cff221ee | 569 | forwardReply(); |
000e664b | 570 | } else |
cff221ee AR |
571 | forwardError(); |
572 | } | |
573 | ||
434a79b0 | 574 | void |
5517260a | 575 | Ftp::Relay::readDataReply() |
434a79b0 | 576 | { |
e7ce227f AR |
577 | assert(serverState() == fssHandleDataRequest || |
578 | serverState() == fssHandleUploadRequest); | |
a5d444a5 | 579 | |
6af7d28d | 580 | if (ctrl.replycode == 125 || ctrl.replycode == 150) { |
e7ce227f | 581 | if (serverState() == fssHandleDataRequest) |
5517260a | 582 | forwardPreliminaryReply(&Ftp::Relay::startDataDownload); |
ec69bdb2 CT |
583 | else if (fwd->request->forcedBodyContinuation /*&& serverState() == fssHandleUploadRequest*/) |
584 | startDataUpload(); | |
f53969cc | 585 | else // serverState() == fssHandleUploadRequest |
5517260a | 586 | forwardPreliminaryReply(&Ftp::Relay::startDataUpload); |
a5d444a5 | 587 | } else |
434a79b0 | 588 | forwardReply(); |
54a6c0cd CT |
589 | } |
590 | ||
591 | bool | |
5517260a | 592 | Ftp::Relay::startDirTracking() |
54a6c0cd CT |
593 | { |
594 | if (!fwd->request->clientConnectionManager->port->ftp_track_dirs) | |
595 | return false; | |
596 | ||
e7ce227f | 597 | debugs(9, 5, "start directory tracking"); |
54a6c0cd CT |
598 | savedReply.message = ctrl.message; |
599 | savedReply.lastCommand = ctrl.last_command; | |
600 | savedReply.lastReply = ctrl.last_reply; | |
601 | savedReply.replyCode = ctrl.replycode; | |
602 | ||
603 | ctrl.last_command = NULL; | |
604 | ctrl.last_reply = NULL; | |
605 | ctrl.message = NULL; | |
606 | ctrl.offset = 0; | |
607 | writeCommand("PWD\r\n"); | |
608 | return true; | |
609 | } | |
610 | ||
611 | void | |
5517260a | 612 | Ftp::Relay::stopDirTracking() |
54a6c0cd | 613 | { |
e7ce227f | 614 | debugs(9, 5, "got code from pwd: " << ctrl.replycode << ", msg: " << ctrl.last_reply); |
54a6c0cd | 615 | |
92ae4c86 AR |
616 | if (ctrl.replycode == 257) |
617 | updateMaster().workingDir = Ftp::UnescapeDoubleQuoted(ctrl.last_reply); | |
54a6c0cd CT |
618 | |
619 | wordlistDestroy(&ctrl.message); | |
620 | safe_free(ctrl.last_command); | |
621 | safe_free(ctrl.last_reply); | |
622 | ||
623 | ctrl.message = savedReply.message; | |
624 | ctrl.last_command = savedReply.lastCommand; | |
625 | ctrl.last_reply = savedReply.lastReply; | |
626 | ctrl.replycode = savedReply.replyCode; | |
627 | ||
628 | savedReply.message = NULL; | |
629 | savedReply.lastReply = NULL; | |
630 | savedReply.lastCommand = NULL; | |
631 | } | |
632 | ||
633 | void | |
5517260a | 634 | Ftp::Relay::readCwdOrCdupReply() |
54a6c0cd | 635 | { |
e7ce227f AR |
636 | assert(serverState() == fssHandleCwd || |
637 | serverState() == fssHandleCdup); | |
54a6c0cd | 638 | |
e7ce227f | 639 | debugs(9, 5, "got code " << ctrl.replycode << ", msg: " << ctrl.last_reply); |
54a6c0cd CT |
640 | |
641 | if (100 <= ctrl.replycode && ctrl.replycode < 200) | |
642 | return; | |
643 | ||
644 | if (weAreTrackingDir()) { // we are tracking | |
645 | stopDirTracking(); // and forward the delayed response below | |
646 | } else if (startDirTracking()) | |
647 | return; | |
648 | ||
649 | forwardReply(); | |
650 | } | |
651 | ||
652 | void | |
5517260a | 653 | Ftp::Relay::readUserOrPassReply() |
54a6c0cd CT |
654 | { |
655 | if (100 <= ctrl.replycode && ctrl.replycode < 200) | |
656 | return; //Just ignore | |
657 | ||
658 | if (weAreTrackingDir()) { // we are tracking | |
659 | stopDirTracking(); // and forward the delayed response below | |
660 | } else if (ctrl.replycode == 230) { // successful login | |
661 | if (startDirTracking()) | |
662 | return; | |
663 | } | |
664 | ||
665 | forwardReply(); | |
434a79b0 DK |
666 | } |
667 | ||
668 | void | |
5517260a | 669 | Ftp::Relay::readTransferDoneReply() |
434a79b0 | 670 | { |
e7ce227f | 671 | debugs(9, 3, status()); |
434a79b0 DK |
672 | |
673 | if (ctrl.replycode != 226 && ctrl.replycode != 250) { | |
e7ce227f AR |
674 | debugs(9, DBG_IMPORTANT, "got FTP code " << ctrl.replycode << |
675 | " after reading response data"); | |
434a79b0 DK |
676 | } |
677 | ||
434a79b0 DK |
678 | serverComplete(); |
679 | } | |
680 | ||
681 | void | |
43446566 | 682 | Ftp::Relay::dataChannelConnected(const CommConnectCbParams &io) |
434a79b0 | 683 | { |
e7ce227f | 684 | debugs(9, 3, status()); |
434a79b0 DK |
685 | data.opener = NULL; |
686 | ||
43446566 | 687 | if (io.flag != Comm::OK) { |
e7ce227f | 688 | debugs(9, 2, "failed to connect FTP server data channel"); |
43446566 | 689 | forwardError(ERR_CONNECT_FAIL, io.xerrno); |
434a79b0 DK |
690 | return; |
691 | } | |
692 | ||
43446566 | 693 | debugs(9, 2, "connected FTP server data channel: " << io.conn); |
434a79b0 | 694 | |
43446566 | 695 | data.opened(io.conn, dataCloser()); |
434a79b0 DK |
696 | |
697 | sendCommand(); | |
698 | } | |
699 | ||
700 | void | |
5517260a | 701 | Ftp::Relay::scheduleReadControlReply() |
434a79b0 | 702 | { |
5517260a | 703 | Ftp::Client::scheduleReadControlReply(0); |
434a79b0 DK |
704 | } |
705 | ||
5517260a AR |
706 | AsyncJob::Pointer |
707 | Ftp::StartRelay(FwdState *const fwdState) | |
434a79b0 | 708 | { |
5517260a | 709 | return AsyncJob::Start(new Ftp::Relay(fwdState)); |
434a79b0 | 710 | } |
f53969cc | 711 |