]>
Commit | Line | Data |
---|---|---|
434a79b0 | 1 | /* |
bbc27441 | 2 | * Copyright (C) 1996-2014 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" |
434a79b0 | 21 | #include "Server.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 DK |
34 | { |
35 | public: | |
5517260a AR |
36 | explicit Relay(FwdState *const fwdState); |
37 | virtual ~Relay(); | |
434a79b0 DK |
38 | |
39 | protected: | |
92ae4c86 AR |
40 | const Ftp::MasterState &master() const; |
41 | Ftp::MasterState &updateMaster(); | |
e7ce227f AR |
42 | Ftp::ServerState serverState() const { return master().serverState; } |
43 | void serverState(const Ftp::ServerState newState); | |
92ae4c86 | 44 | |
5517260a | 45 | /* Ftp::Client API */ |
434a79b0 | 46 | virtual void failed(err_type error = ERR_NONE, int xerrno = 0); |
43446566 | 47 | virtual void dataChannelConnected(const CommConnectCbParams &io); |
5517260a AR |
48 | |
49 | /* ServerStateData API */ | |
50 | virtual void serverComplete(); | |
434a79b0 | 51 | virtual void handleControlReply(); |
5517260a | 52 | virtual void processReplyBody(); |
434a79b0 | 53 | virtual void handleRequestBodyProducerAborted(); |
719c885c AR |
54 | virtual bool mayReadVirginReplyBody() const; |
55 | virtual void completeForwarding(); | |
5517260a AR |
56 | |
57 | /* AsyncJob API */ | |
58 | virtual void start(); | |
59 | ||
434a79b0 DK |
60 | void forwardReply(); |
61 | void forwardError(err_type error = ERR_NONE, int xerrno = 0); | |
719c885c | 62 | void failedErrorMessage(err_type error, int xerrno); |
43446566 | 63 | HttpReply *createHttpReply(const Http::StatusCode httpStatus, const int64_t clen = 0); |
434a79b0 | 64 | void handleDataRequest(); |
a5d444a5 DK |
65 | void startDataDownload(); |
66 | void startDataUpload(); | |
54a6c0cd CT |
67 | bool startDirTracking(); |
68 | void stopDirTracking(); | |
69 | bool weAreTrackingDir() const {return savedReply.message != NULL;} | |
434a79b0 | 70 | |
5517260a | 71 | typedef void (Relay::*PreliminaryCb)(); |
434a79b0 DK |
72 | void forwardPreliminaryReply(const PreliminaryCb cb); |
73 | void proceedAfterPreliminaryReply(); | |
74 | PreliminaryCb thePreliminaryCb; | |
75 | ||
5517260a | 76 | typedef void (Relay::*SM_FUNC)(); |
434a79b0 | 77 | static const SM_FUNC SM_FUNCS[]; |
125894ba | 78 | void readGreeting(); |
434a79b0 DK |
79 | void sendCommand(); |
80 | void readReply(); | |
5f3898e2 | 81 | void readFeatReply(); |
434a79b0 DK |
82 | void readPasvReply(); |
83 | void readDataReply(); | |
84 | void readTransferDoneReply(); | |
000e664b | 85 | void readEpsvReply(); |
54a6c0cd CT |
86 | void readCwdOrCdupReply(); |
87 | void readUserOrPassReply(); | |
434a79b0 | 88 | |
434a79b0 DK |
89 | void scheduleReadControlReply(); |
90 | ||
719c885c AR |
91 | bool forwardingCompleted; ///< completeForwarding() has been called |
92 | ||
54a6c0cd CT |
93 | struct { |
94 | wordlist *message; ///< reply message, one wordlist entry per message line | |
95 | char *lastCommand; ///< the command caused the reply | |
96 | char *lastReply; ///< last line of reply: reply status plus message | |
97 | int replyCode; ///< the reply status | |
98 | } savedReply; ///< set and delayed while we are tracking using PWD | |
99 | ||
5517260a | 100 | CBDATA_CLASS2(Relay); |
434a79b0 DK |
101 | }; |
102 | ||
5517260a | 103 | } // namespace Ftp |
434a79b0 | 104 | |
5517260a AR |
105 | CBDATA_NAMESPACED_CLASS_INIT(Ftp, Relay); |
106 | ||
107 | const Ftp::Relay::SM_FUNC Ftp::Relay::SM_FUNCS[] = { | |
108 | &Ftp::Relay::readGreeting, // BEGIN | |
109 | &Ftp::Relay::readUserOrPassReply, // SENT_USER | |
110 | &Ftp::Relay::readUserOrPassReply, // SENT_PASS | |
e7ce227f AR |
111 | NULL,/* &Ftp::Relay::readReply */ // SENT_TYPE |
112 | NULL,/* &Ftp::Relay::readReply */ // SENT_MDTM | |
113 | NULL,/* &Ftp::Relay::readReply */ // SENT_SIZE | |
000e664b AR |
114 | NULL, // SENT_EPRT |
115 | NULL, // SENT_PORT | |
5517260a AR |
116 | &Ftp::Relay::readEpsvReply, // SENT_EPSV_ALL |
117 | &Ftp::Relay::readEpsvReply, // SENT_EPSV_1 | |
118 | &Ftp::Relay::readEpsvReply, // SENT_EPSV_2 | |
119 | &Ftp::Relay::readPasvReply, // SENT_PASV | |
120 | &Ftp::Relay::readCwdOrCdupReply, // SENT_CWD | |
e7ce227f AR |
121 | NULL,/* &Ftp::Relay::readDataReply, */ // SENT_LIST |
122 | NULL,/* &Ftp::Relay::readDataReply, */ // SENT_NLST | |
123 | NULL,/* &Ftp::Relay::readReply */ // SENT_REST | |
124 | NULL,/* &Ftp::Relay::readDataReply */ // SENT_RETR | |
125 | NULL,/* &Ftp::Relay::readReply */ // SENT_STOR | |
126 | NULL,/* &Ftp::Relay::readReply */ // SENT_QUIT | |
5517260a AR |
127 | &Ftp::Relay::readTransferDoneReply, // READING_DATA |
128 | &Ftp::Relay::readReply, // WRITING_DATA | |
e7ce227f | 129 | NULL,/* &Ftp::Relay::readReply */ // SENT_MKDIR |
5517260a | 130 | &Ftp::Relay::readFeatReply, // SENT_FEAT |
e7ce227f | 131 | NULL,/* &Ftp::Relay::readPwdReply */ // SENT_PWD |
5517260a AR |
132 | &Ftp::Relay::readCwdOrCdupReply, // SENT_CDUP |
133 | &Ftp::Relay::readDataReply,// SENT_DATA_REQUEST | |
134 | &Ftp::Relay::readReply, // SENT_COMMAND | |
000e664b | 135 | NULL |
434a79b0 DK |
136 | }; |
137 | ||
5517260a AR |
138 | Ftp::Relay::Relay(FwdState *const fwdState): |
139 | AsyncJob("Ftp::Relay"), | |
140 | Ftp::Client(fwdState), | |
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 | { |
29f23abf | 155 | closeServer(); // TODO: move to Server.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 DK |
329 | { |
330 | ::ServerStateData::handleRequestBodyProducerAborted(); | |
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); |
434a79b0 DK |
349 | |
350 | setVirginReply(reply); | |
351 | adaptOrFinalizeReply(); | |
352 | ||
434a79b0 DK |
353 | serverComplete(); |
354 | } | |
355 | ||
356 | void | |
5517260a | 357 | Ftp::Relay::forwardPreliminaryReply(const PreliminaryCb cb) |
434a79b0 | 358 | { |
e7ce227f | 359 | debugs(9, 5, "forwarding preliminary reply to client"); |
434a79b0 | 360 | |
92ae4c86 AR |
361 | // we must prevent concurrent ConnStateData::sendControlMsg() calls |
362 | Must(thePreliminaryCb == NULL); | |
434a79b0 DK |
363 | thePreliminaryCb = cb; |
364 | ||
365 | const HttpReply::Pointer reply = createHttpReply(Http::scContinue); | |
366 | ||
367 | // the Sink will use this to call us back after writing 1xx to the client | |
5517260a | 368 | typedef NullaryMemFunT<Relay> CbDialer; |
434a79b0 | 369 | const AsyncCall::Pointer call = JobCallback(11, 3, CbDialer, this, |
27c841f6 | 370 | Ftp::Relay::proceedAfterPreliminaryReply); |
434a79b0 DK |
371 | |
372 | CallJobHere1(9, 4, request->clientConnectionManager, ConnStateData, | |
373 | ConnStateData::sendControlMsg, HttpControlMsg(reply, call)); | |
374 | } | |
375 | ||
376 | void | |
5517260a | 377 | Ftp::Relay::proceedAfterPreliminaryReply() |
434a79b0 | 378 | { |
e7ce227f | 379 | debugs(9, 5, "proceeding after preliminary reply to client"); |
434a79b0 | 380 | |
92ae4c86 | 381 | Must(thePreliminaryCb != NULL); |
434a79b0 DK |
382 | const PreliminaryCb cb = thePreliminaryCb; |
383 | thePreliminaryCb = NULL; | |
384 | (this->*cb)(); | |
385 | } | |
386 | ||
387 | void | |
5517260a | 388 | Ftp::Relay::forwardError(err_type error, int xerrno) |
434a79b0 | 389 | { |
434a79b0 DK |
390 | failed(error, xerrno); |
391 | } | |
392 | ||
393 | HttpReply * | |
43446566 AR |
394 | Ftp::Relay::createHttpReply(const Http::StatusCode httpStatus, const int64_t clen) |
395 | { | |
396 | HttpReply *const reply = Ftp::HttpReplyWrapper(ctrl.replycode, ctrl.last_reply, httpStatus, clen); | |
a2c7f09a AR |
397 | if (ctrl.message) { |
398 | for (wordlist *W = ctrl.message; W && W->next; W = W->next) | |
43446566 AR |
399 | reply->header.putStr(HDR_FTP_PRE, httpHeaderQuoteString(W->key).c_str()); |
400 | // no hdrCacheInit() is needed for after HDR_FTP_PRE addition | |
a2c7f09a | 401 | } |
434a79b0 DK |
402 | return reply; |
403 | } | |
404 | ||
405 | void | |
5517260a | 406 | Ftp::Relay::handleDataRequest() |
434a79b0 | 407 | { |
92ae4c86 | 408 | data.addr(master().clientDataAddr); |
434a79b0 DK |
409 | connectDataChannel(); |
410 | } | |
411 | ||
412 | void | |
5517260a | 413 | Ftp::Relay::startDataDownload() |
434a79b0 DK |
414 | { |
415 | assert(Comm::IsConnOpen(data.conn)); | |
416 | ||
e7ce227f | 417 | debugs(9, 3, "begin data transfer from " << data.conn->remote << |
434a79b0 DK |
418 | " (" << data.conn->local << ")"); |
419 | ||
420 | HttpReply *const reply = createHttpReply(Http::scOkay, -1); | |
421 | EBIT_CLR(entry->flags, ENTRY_FWD_HDR_WAIT); | |
422 | setVirginReply(reply); | |
423 | adaptOrFinalizeReply(); | |
424 | ||
434a79b0 DK |
425 | maybeReadVirginBody(); |
426 | state = READING_DATA; | |
427 | } | |
428 | ||
a5d444a5 | 429 | void |
5517260a | 430 | Ftp::Relay::startDataUpload() |
a5d444a5 DK |
431 | { |
432 | assert(Comm::IsConnOpen(data.conn)); | |
433 | ||
e7ce227f | 434 | debugs(9, 3, "begin data transfer to " << data.conn->remote << |
a5d444a5 DK |
435 | " (" << data.conn->local << ")"); |
436 | ||
437 | if (!startRequestBodyFlow()) { // register to receive body data | |
438 | failed(); | |
439 | return; | |
440 | } | |
441 | ||
000e664b | 442 | state = WRITING_DATA; |
a5d444a5 DK |
443 | } |
444 | ||
434a79b0 | 445 | void |
5517260a | 446 | Ftp::Relay::readGreeting() |
434a79b0 | 447 | { |
92ae4c86 | 448 | assert(!master().clientReadGreeting); |
434a79b0 DK |
449 | |
450 | switch (ctrl.replycode) { | |
451 | case 220: | |
92ae4c86 | 452 | updateMaster().clientReadGreeting = true; |
e7ce227f AR |
453 | if (serverState() == fssBegin) |
454 | serverState(fssConnected); | |
29268829 | 455 | |
43446566 AR |
456 | // Do not forward server greeting to the user because our FTP Server |
457 | // has greeted the user already. Also, an original origin greeting may | |
458 | // confuse a user that has changed the origin mid-air. | |
29268829 DK |
459 | |
460 | start(); | |
434a79b0 DK |
461 | break; |
462 | case 120: | |
463 | if (NULL != ctrl.message) | |
464 | debugs(9, DBG_IMPORTANT, "FTP server is busy: " << ctrl.message->key); | |
5517260a | 465 | forwardPreliminaryReply(&Ftp::Relay::scheduleReadControlReply); |
434a79b0 DK |
466 | break; |
467 | default: | |
468 | failed(); | |
469 | break; | |
470 | } | |
471 | } | |
472 | ||
473 | void | |
5517260a | 474 | Ftp::Relay::sendCommand() |
434a79b0 DK |
475 | { |
476 | if (!fwd->request->header.has(HDR_FTP_COMMAND)) { | |
43446566 | 477 | abortTransaction("Internal error: FTP relay request with no command"); |
434a79b0 DK |
478 | return; |
479 | } | |
480 | ||
c8059ea9 DK |
481 | HttpHeader &header = fwd->request->header; |
482 | assert(header.has(HDR_FTP_COMMAND)); | |
483 | const String &cmd = header.findEntry(HDR_FTP_COMMAND)->value; | |
484 | assert(header.has(HDR_FTP_ARGUMENTS)); | |
485 | const String ¶ms = header.findEntry(HDR_FTP_ARGUMENTS)->value; | |
486 | ||
487 | if (params.size() > 0) | |
e7ce227f | 488 | debugs(9, 5, "command: " << cmd << ", parameters: " << params); |
434a79b0 | 489 | else |
e7ce227f | 490 | debugs(9, 5, "command: " << cmd << ", no parameters"); |
434a79b0 | 491 | |
e7ce227f | 492 | if (serverState() == fssHandlePasv || |
27c841f6 AR |
493 | serverState() == fssHandleEpsv || |
494 | serverState() == fssHandleEprt || | |
495 | serverState() == fssHandlePort) { | |
000e664b AR |
496 | sendPassive(); |
497 | return; | |
498 | } | |
499 | ||
1ab04517 | 500 | SBuf buf; |
c8059ea9 | 501 | if (params.size() > 0) |
1ab04517 | 502 | buf.Printf("%s %s%s", cmd.termedBuf(), params.termedBuf(), Ftp::crlf); |
434a79b0 | 503 | else |
1ab04517 | 504 | buf.Printf("%s%s", cmd.termedBuf(), Ftp::crlf); |
434a79b0 | 505 | |
1ab04517 | 506 | writeCommand(buf.c_str()); |
434a79b0 | 507 | |
cff221ee | 508 | state = |
e7ce227f AR |
509 | serverState() == fssHandleCdup ? SENT_CDUP : |
510 | serverState() == fssHandleCwd ? SENT_CWD : | |
511 | serverState() == fssHandleFeat ? SENT_FEAT : | |
512 | serverState() == fssHandleDataRequest ? SENT_DATA_REQUEST : | |
513 | serverState() == fssHandleUploadRequest ? SENT_DATA_REQUEST : | |
514 | serverState() == fssConnected ? SENT_USER : | |
515 | serverState() == fssHandlePass ? SENT_PASS : | |
434a79b0 DK |
516 | SENT_COMMAND; |
517 | } | |
518 | ||
519 | void | |
5517260a | 520 | Ftp::Relay::readReply() |
434a79b0 | 521 | { |
e7ce227f AR |
522 | assert(serverState() == fssConnected || |
523 | serverState() == fssHandleUploadRequest); | |
434a79b0 DK |
524 | |
525 | if (100 <= ctrl.replycode && ctrl.replycode < 200) | |
5517260a | 526 | forwardPreliminaryReply(&Ftp::Relay::scheduleReadControlReply); |
434a79b0 DK |
527 | else |
528 | forwardReply(); | |
529 | } | |
530 | ||
5f3898e2 | 531 | void |
5517260a | 532 | Ftp::Relay::readFeatReply() |
5f3898e2 | 533 | { |
e7ce227f | 534 | assert(serverState() == fssHandleFeat); |
5f3898e2 AR |
535 | |
536 | if (100 <= ctrl.replycode && ctrl.replycode < 200) | |
537 | return; // ignore preliminary replies | |
538 | ||
539 | forwardReply(); | |
540 | } | |
541 | ||
434a79b0 | 542 | void |
5517260a | 543 | Ftp::Relay::readPasvReply() |
434a79b0 | 544 | { |
e7ce227f | 545 | assert(serverState() == fssHandlePasv || serverState() == fssHandleEpsv || serverState() == fssHandlePort || serverState() == fssHandleEprt); |
434a79b0 DK |
546 | |
547 | if (100 <= ctrl.replycode && ctrl.replycode < 200) | |
548 | return; // ignore preliminary replies | |
549 | ||
92ae4c86 | 550 | if (handlePasvReply(updateMaster().clientDataAddr)) |
434a79b0 | 551 | forwardReply(); |
73950ceb | 552 | else |
434a79b0 DK |
553 | forwardError(); |
554 | } | |
555 | ||
cff221ee | 556 | void |
5517260a | 557 | Ftp::Relay::readEpsvReply() |
cff221ee | 558 | { |
cff221ee AR |
559 | if (100 <= ctrl.replycode && ctrl.replycode < 200) |
560 | return; // ignore preliminary replies | |
561 | ||
92ae4c86 | 562 | if (handleEpsvReply(updateMaster().clientDataAddr)) { |
000e664b AR |
563 | if (ctrl.message == NULL) |
564 | return; // didn't get complete reply yet | |
565 | ||
cff221ee | 566 | forwardReply(); |
000e664b | 567 | } else |
cff221ee AR |
568 | forwardError(); |
569 | } | |
570 | ||
434a79b0 | 571 | void |
5517260a | 572 | Ftp::Relay::readDataReply() |
434a79b0 | 573 | { |
e7ce227f AR |
574 | assert(serverState() == fssHandleDataRequest || |
575 | serverState() == fssHandleUploadRequest); | |
a5d444a5 | 576 | |
6af7d28d | 577 | if (ctrl.replycode == 125 || ctrl.replycode == 150) { |
e7ce227f | 578 | if (serverState() == fssHandleDataRequest) |
5517260a | 579 | forwardPreliminaryReply(&Ftp::Relay::startDataDownload); |
e7ce227f | 580 | else // serverState() == fssHandleUploadRequest |
5517260a | 581 | forwardPreliminaryReply(&Ftp::Relay::startDataUpload); |
a5d444a5 | 582 | } else |
434a79b0 | 583 | forwardReply(); |
54a6c0cd CT |
584 | } |
585 | ||
586 | bool | |
5517260a | 587 | Ftp::Relay::startDirTracking() |
54a6c0cd CT |
588 | { |
589 | if (!fwd->request->clientConnectionManager->port->ftp_track_dirs) | |
590 | return false; | |
591 | ||
e7ce227f | 592 | debugs(9, 5, "start directory tracking"); |
54a6c0cd CT |
593 | savedReply.message = ctrl.message; |
594 | savedReply.lastCommand = ctrl.last_command; | |
595 | savedReply.lastReply = ctrl.last_reply; | |
596 | savedReply.replyCode = ctrl.replycode; | |
597 | ||
598 | ctrl.last_command = NULL; | |
599 | ctrl.last_reply = NULL; | |
600 | ctrl.message = NULL; | |
601 | ctrl.offset = 0; | |
602 | writeCommand("PWD\r\n"); | |
603 | return true; | |
604 | } | |
605 | ||
606 | void | |
5517260a | 607 | Ftp::Relay::stopDirTracking() |
54a6c0cd | 608 | { |
e7ce227f | 609 | debugs(9, 5, "got code from pwd: " << ctrl.replycode << ", msg: " << ctrl.last_reply); |
54a6c0cd | 610 | |
92ae4c86 AR |
611 | if (ctrl.replycode == 257) |
612 | updateMaster().workingDir = Ftp::UnescapeDoubleQuoted(ctrl.last_reply); | |
54a6c0cd CT |
613 | |
614 | wordlistDestroy(&ctrl.message); | |
615 | safe_free(ctrl.last_command); | |
616 | safe_free(ctrl.last_reply); | |
617 | ||
618 | ctrl.message = savedReply.message; | |
619 | ctrl.last_command = savedReply.lastCommand; | |
620 | ctrl.last_reply = savedReply.lastReply; | |
621 | ctrl.replycode = savedReply.replyCode; | |
622 | ||
623 | savedReply.message = NULL; | |
624 | savedReply.lastReply = NULL; | |
625 | savedReply.lastCommand = NULL; | |
626 | } | |
627 | ||
628 | void | |
5517260a | 629 | Ftp::Relay::readCwdOrCdupReply() |
54a6c0cd | 630 | { |
e7ce227f AR |
631 | assert(serverState() == fssHandleCwd || |
632 | serverState() == fssHandleCdup); | |
54a6c0cd | 633 | |
e7ce227f | 634 | debugs(9, 5, "got code " << ctrl.replycode << ", msg: " << ctrl.last_reply); |
54a6c0cd CT |
635 | |
636 | if (100 <= ctrl.replycode && ctrl.replycode < 200) | |
637 | return; | |
638 | ||
639 | if (weAreTrackingDir()) { // we are tracking | |
640 | stopDirTracking(); // and forward the delayed response below | |
641 | } else if (startDirTracking()) | |
642 | return; | |
643 | ||
644 | forwardReply(); | |
645 | } | |
646 | ||
647 | void | |
5517260a | 648 | Ftp::Relay::readUserOrPassReply() |
54a6c0cd CT |
649 | { |
650 | if (100 <= ctrl.replycode && ctrl.replycode < 200) | |
651 | return; //Just ignore | |
652 | ||
653 | if (weAreTrackingDir()) { // we are tracking | |
654 | stopDirTracking(); // and forward the delayed response below | |
655 | } else if (ctrl.replycode == 230) { // successful login | |
656 | if (startDirTracking()) | |
657 | return; | |
658 | } | |
659 | ||
660 | forwardReply(); | |
434a79b0 DK |
661 | } |
662 | ||
663 | void | |
5517260a | 664 | Ftp::Relay::readTransferDoneReply() |
434a79b0 | 665 | { |
e7ce227f | 666 | debugs(9, 3, status()); |
434a79b0 DK |
667 | |
668 | if (ctrl.replycode != 226 && ctrl.replycode != 250) { | |
e7ce227f AR |
669 | debugs(9, DBG_IMPORTANT, "got FTP code " << ctrl.replycode << |
670 | " after reading response data"); | |
434a79b0 DK |
671 | } |
672 | ||
434a79b0 DK |
673 | serverComplete(); |
674 | } | |
675 | ||
676 | void | |
43446566 | 677 | Ftp::Relay::dataChannelConnected(const CommConnectCbParams &io) |
434a79b0 | 678 | { |
e7ce227f | 679 | debugs(9, 3, status()); |
434a79b0 DK |
680 | data.opener = NULL; |
681 | ||
43446566 | 682 | if (io.flag != Comm::OK) { |
e7ce227f | 683 | debugs(9, 2, "failed to connect FTP server data channel"); |
43446566 | 684 | forwardError(ERR_CONNECT_FAIL, io.xerrno); |
434a79b0 DK |
685 | return; |
686 | } | |
687 | ||
43446566 | 688 | debugs(9, 2, "connected FTP server data channel: " << io.conn); |
434a79b0 | 689 | |
43446566 | 690 | data.opened(io.conn, dataCloser()); |
434a79b0 DK |
691 | |
692 | sendCommand(); | |
693 | } | |
694 | ||
695 | void | |
5517260a | 696 | Ftp::Relay::scheduleReadControlReply() |
434a79b0 | 697 | { |
5517260a | 698 | Ftp::Client::scheduleReadControlReply(0); |
434a79b0 DK |
699 | } |
700 | ||
5517260a AR |
701 | AsyncJob::Pointer |
702 | Ftp::StartRelay(FwdState *const fwdState) | |
434a79b0 | 703 | { |
5517260a | 704 | return AsyncJob::Start(new Ftp::Relay(fwdState)); |
434a79b0 | 705 | } |