]>
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" |
f8e4867b | 12 | #include "acl/FilledChecklist.h" |
434a79b0 | 13 | #include "client_side.h" |
27c841f6 | 14 | #include "clients/FtpClient.h" |
434a79b0 | 15 | #include "comm/ConnOpener.h" |
8ea0d847 | 16 | #include "comm/Read.h" |
000e664b | 17 | #include "comm/TcpAcceptor.h" |
434a79b0 DK |
18 | #include "comm/Write.h" |
19 | #include "errorpage.h" | |
20 | #include "fd.h" | |
92ae4c86 | 21 | #include "ftp/Parsing.h" |
000e664b | 22 | #include "ip/tools.h" |
27c841f6 | 23 | #include "SquidConfig.h" |
54a6c0cd | 24 | #include "SquidString.h" |
27c841f6 | 25 | #include "StatCounters.h" |
434a79b0 DK |
26 | #include "tools.h" |
27 | #include "wordlist.h" | |
3cc0f4e7 | 28 | |
54a6c0cd | 29 | #include <set> |
434a79b0 | 30 | |
27c841f6 AR |
31 | namespace Ftp |
32 | { | |
434a79b0 DK |
33 | |
34 | const char *const crlf = "\r\n"; | |
35 | ||
434a79b0 DK |
36 | static char * |
37 | escapeIAC(const char *buf) | |
38 | { | |
39 | int n; | |
40 | char *ret; | |
41 | unsigned const char *p; | |
42 | unsigned char *r; | |
43 | ||
44 | for (p = (unsigned const char *)buf, n = 1; *p; ++n, ++p) | |
45 | if (*p == 255) | |
46 | ++n; | |
47 | ||
48 | ret = (char *)xmalloc(n); | |
49 | ||
50 | for (p = (unsigned const char *)buf, r=(unsigned char *)ret; *p; ++p) { | |
51 | *r = *p; | |
52 | ++r; | |
53 | ||
54 | if (*p == 255) { | |
55 | *r = 255; | |
56 | ++r; | |
57 | } | |
58 | } | |
59 | ||
60 | *r = '\0'; | |
61 | ++r; | |
62 | assert((r - (unsigned char *)ret) == n ); | |
63 | return ret; | |
64 | } | |
65 | ||
e7ce227f AR |
66 | /* Ftp::Channel */ |
67 | ||
434a79b0 DK |
68 | /// configures the channel with a descriptor and registers a close handler |
69 | void | |
5517260a | 70 | Ftp::Channel::opened(const Comm::ConnectionPointer &newConn, |
27c841f6 | 71 | const AsyncCall::Pointer &aCloser) |
434a79b0 DK |
72 | { |
73 | assert(!Comm::IsConnOpen(conn)); | |
74 | assert(closer == NULL); | |
75 | ||
76 | assert(Comm::IsConnOpen(newConn)); | |
77 | assert(aCloser != NULL); | |
78 | ||
79 | conn = newConn; | |
80 | closer = aCloser; | |
81 | comm_add_close_handler(conn->fd, closer); | |
82 | } | |
83 | ||
84 | /// planned close: removes the close handler and calls comm_close | |
85 | void | |
5517260a | 86 | Ftp::Channel::close() |
434a79b0 DK |
87 | { |
88 | // channels with active listeners will be closed when the listener handler dies. | |
89 | if (Comm::IsConnOpen(conn)) { | |
90 | comm_remove_close_handler(conn->fd, closer); | |
91 | conn->close(); // we do not expect to be called back | |
92 | } | |
93 | clear(); | |
94 | } | |
95 | ||
96 | void | |
5517260a | 97 | Ftp::Channel::forget() |
434a79b0 | 98 | { |
5948d8e9 AR |
99 | if (Comm::IsConnOpen(conn)) { |
100 | commUnsetConnTimeout(conn); | |
434a79b0 | 101 | comm_remove_close_handler(conn->fd, closer); |
5948d8e9 | 102 | } |
29f23abf | 103 | clear(); |
434a79b0 DK |
104 | } |
105 | ||
106 | void | |
5517260a | 107 | Ftp::Channel::clear() |
434a79b0 DK |
108 | { |
109 | conn = NULL; | |
110 | closer = NULL; | |
111 | } | |
112 | ||
e7ce227f AR |
113 | /* Ftp::CtrlChannel */ |
114 | ||
115 | Ftp::CtrlChannel::CtrlChannel(): | |
f53969cc SM |
116 | buf(NULL), |
117 | size(0), | |
118 | offset(0), | |
119 | message(NULL), | |
120 | last_command(NULL), | |
121 | last_reply(NULL), | |
122 | replycode(0) | |
434a79b0 | 123 | { |
e7ce227f AR |
124 | buf = static_cast<char*>(memAllocBuf(4096, &size)); |
125 | } | |
434a79b0 | 126 | |
e7ce227f AR |
127 | Ftp::CtrlChannel::~CtrlChannel() |
128 | { | |
129 | memFreeBuf(size, buf); | |
130 | if (message) | |
131 | wordlistDestroy(&message); | |
132 | safe_free(last_command); | |
133 | safe_free(last_reply); | |
134 | } | |
434a79b0 | 135 | |
e7ce227f AR |
136 | /* Ftp::DataChannel */ |
137 | ||
138 | Ftp::DataChannel::DataChannel(): | |
f53969cc SM |
139 | readBuf(NULL), |
140 | host(NULL), | |
141 | port(0), | |
142 | read_pending(false) | |
e7ce227f AR |
143 | { |
144 | } | |
145 | ||
146 | Ftp::DataChannel::~DataChannel() | |
147 | { | |
148 | delete readBuf; | |
434a79b0 DK |
149 | } |
150 | ||
73950ceb | 151 | void |
e7ce227f | 152 | Ftp::DataChannel::addr(const Ip::Address &import) |
73950ceb | 153 | { |
27c841f6 AR |
154 | static char addrBuf[MAX_IPSTRLEN]; |
155 | import.toStr(addrBuf, sizeof(addrBuf)); | |
156 | xfree(host); | |
157 | host = xstrdup(addrBuf); | |
158 | port = import.port(); | |
73950ceb AR |
159 | } |
160 | ||
e7ce227f AR |
161 | /* Ftp::Client */ |
162 | ||
163 | Ftp::Client::Client(FwdState *fwdState): | |
f53969cc SM |
164 | AsyncJob("Ftp::Client"), |
165 | ::Client(fwdState), | |
166 | ctrl(), | |
167 | data(), | |
168 | state(BEGIN), | |
169 | old_request(NULL), | |
170 | old_reply(NULL), | |
171 | shortenReadTimeout(false) | |
e7ce227f AR |
172 | { |
173 | ++statCounter.server.all.requests; | |
174 | ++statCounter.server.ftp.requests; | |
175 | ||
176 | ctrl.last_command = xstrdup("Connect to server"); | |
177 | ||
178 | typedef CommCbMemFunT<Client, CommCloseCbParams> Dialer; | |
179 | const AsyncCall::Pointer closer = JobCallback(9, 5, Dialer, this, | |
27c841f6 | 180 | Ftp::Client::ctrlClosed); |
e7ce227f AR |
181 | ctrl.opened(fwdState->serverConnection(), closer); |
182 | } | |
183 | ||
5517260a | 184 | Ftp::Client::~Client() |
434a79b0 DK |
185 | { |
186 | if (data.opener != NULL) { | |
5517260a | 187 | data.opener->cancel("Ftp::Client destructed"); |
434a79b0 DK |
188 | data.opener = NULL; |
189 | } | |
190 | data.close(); | |
191 | ||
434a79b0 | 192 | safe_free(old_request); |
434a79b0 | 193 | safe_free(old_reply); |
434a79b0 DK |
194 | fwd = NULL; // refcounted |
195 | } | |
196 | ||
197 | void | |
5517260a | 198 | Ftp::Client::start() |
434a79b0 DK |
199 | { |
200 | scheduleReadControlReply(0); | |
201 | } | |
202 | ||
adaff124 | 203 | void |
5517260a | 204 | Ftp::Client::initReadBuf() |
adaff124 DK |
205 | { |
206 | if (data.readBuf == NULL) { | |
207 | data.readBuf = new MemBuf; | |
208 | data.readBuf->init(4096, SQUID_TCP_SO_RCVBUF); | |
209 | } | |
210 | } | |
211 | ||
434a79b0 DK |
212 | /** |
213 | * Close the FTP server connection(s). Used by serverComplete(). | |
214 | */ | |
215 | void | |
5517260a | 216 | Ftp::Client::closeServer() |
434a79b0 DK |
217 | { |
218 | if (Comm::IsConnOpen(ctrl.conn)) { | |
e7ce227f | 219 | debugs(9, 3, "closing FTP server FD " << ctrl.conn->fd << ", this " << this); |
434a79b0 DK |
220 | fwd->unregister(ctrl.conn); |
221 | ctrl.close(); | |
222 | } | |
223 | ||
224 | if (Comm::IsConnOpen(data.conn)) { | |
e7ce227f | 225 | debugs(9, 3, "closing FTP data FD " << data.conn->fd << ", this " << this); |
434a79b0 DK |
226 | data.close(); |
227 | } | |
228 | ||
e7ce227f | 229 | debugs(9, 3, "FTP ctrl and data connections closed. this " << this); |
434a79b0 DK |
230 | } |
231 | ||
232 | /** | |
233 | * Did we close all FTP server connection(s)? | |
234 | * | |
e7ce227f AR |
235 | \retval true Both server control and data channels are closed. And not waiting for a new data connection to open. |
236 | \retval false Either control channel or data is still active. | |
434a79b0 DK |
237 | */ |
238 | bool | |
5517260a | 239 | Ftp::Client::doneWithServer() const |
434a79b0 DK |
240 | { |
241 | return !Comm::IsConnOpen(ctrl.conn) && !Comm::IsConnOpen(data.conn); | |
242 | } | |
243 | ||
244 | void | |
5517260a | 245 | Ftp::Client::failed(err_type error, int xerrno) |
434a79b0 | 246 | { |
e7ce227f | 247 | debugs(9, 3, "entry-null=" << (entry?entry->isEmpty():0) << ", entry=" << entry); |
434a79b0 | 248 | |
434a79b0 DK |
249 | const char *command, *reply; |
250 | const Http::StatusCode httpStatus = failedHttpStatus(error); | |
251 | ErrorState *const ftperr = new ErrorState(error, httpStatus, fwd->request); | |
252 | ftperr->xerrno = xerrno; | |
253 | ||
254 | ftperr->ftp.server_msg = ctrl.message; | |
255 | ctrl.message = NULL; | |
256 | ||
257 | if (old_request) | |
258 | command = old_request; | |
259 | else | |
260 | command = ctrl.last_command; | |
261 | ||
262 | if (command && strncmp(command, "PASS", 4) == 0) | |
263 | command = "PASS <yourpassword>"; | |
264 | ||
265 | if (old_reply) | |
266 | reply = old_reply; | |
267 | else | |
268 | reply = ctrl.last_reply; | |
269 | ||
270 | if (command) | |
271 | ftperr->ftp.request = xstrdup(command); | |
272 | ||
273 | if (reply) | |
274 | ftperr->ftp.reply = xstrdup(reply); | |
275 | ||
000e664b | 276 | fwd->request->detailError(error, xerrno); |
719c885c AR |
277 | fwd->fail(ftperr); |
278 | ||
279 | closeServer(); // we failed, so no serverComplete() | |
434a79b0 DK |
280 | } |
281 | ||
282 | Http::StatusCode | |
5517260a | 283 | Ftp::Client::failedHttpStatus(err_type &error) |
434a79b0 DK |
284 | { |
285 | if (error == ERR_NONE) | |
286 | error = ERR_FTP_FAILURE; | |
f8e4867b | 287 | return error == ERR_READ_TIMEOUT ? Http::scGatewayTimeout : |
27c841f6 | 288 | Http::scBadGateway; |
434a79b0 DK |
289 | } |
290 | ||
291 | /** | |
292 | * DPW 2007-04-23 | |
293 | * Looks like there are no longer anymore callers that set | |
294 | * buffered_ok=1. Perhaps it can be removed at some point. | |
295 | */ | |
296 | void | |
5517260a | 297 | Ftp::Client::scheduleReadControlReply(int buffered_ok) |
434a79b0 | 298 | { |
e7ce227f | 299 | debugs(9, 3, ctrl.conn); |
434a79b0 DK |
300 | |
301 | if (buffered_ok && ctrl.offset > 0) { | |
302 | /* We've already read some reply data */ | |
303 | handleControlReply(); | |
304 | } else { | |
305 | /* | |
306 | * Cancel the timeout on the Data socket (if any) and | |
307 | * establish one on the control socket. | |
308 | */ | |
309 | if (Comm::IsConnOpen(data.conn)) { | |
310 | commUnsetConnTimeout(data.conn); | |
311 | } | |
312 | ||
e7ce227f AR |
313 | const time_t tout = shortenReadTimeout ? |
314 | min(Config.Timeout.connect, Config.Timeout.read): | |
315 | Config.Timeout.read; | |
316 | shortenReadTimeout = false; // we only need to do this once, after PASV | |
317 | ||
5517260a AR |
318 | typedef CommCbMemFunT<Client, CommTimeoutCbParams> TimeoutDialer; |
319 | AsyncCall::Pointer timeoutCall = JobCallback(9, 5, TimeoutDialer, this, Ftp::Client::timeout); | |
e7ce227f | 320 | commSetConnTimeout(ctrl.conn, tout, timeoutCall); |
434a79b0 | 321 | |
5517260a AR |
322 | typedef CommCbMemFunT<Client, CommIoCbParams> Dialer; |
323 | AsyncCall::Pointer reader = JobCallback(9, 5, Dialer, this, Ftp::Client::readControlReply); | |
434a79b0 DK |
324 | comm_read(ctrl.conn, ctrl.buf + ctrl.offset, ctrl.size - ctrl.offset, reader); |
325 | } | |
326 | } | |
327 | ||
328 | void | |
5517260a | 329 | Ftp::Client::readControlReply(const CommIoCbParams &io) |
434a79b0 | 330 | { |
e7ce227f | 331 | debugs(9, 3, "FD " << io.fd << ", Read " << io.size << " bytes"); |
434a79b0 DK |
332 | |
333 | if (io.size > 0) { | |
334 | kb_incr(&(statCounter.server.all.kbytes_in), io.size); | |
335 | kb_incr(&(statCounter.server.ftp.kbytes_in), io.size); | |
336 | } | |
337 | ||
8ea0d847 | 338 | if (io.flag == Comm::ERR_CLOSING) |
434a79b0 DK |
339 | return; |
340 | ||
341 | if (EBIT_TEST(entry->flags, ENTRY_ABORTED)) { | |
342 | abortTransaction("entry aborted during control reply read"); | |
343 | return; | |
344 | } | |
345 | ||
346 | assert(ctrl.offset < ctrl.size); | |
347 | ||
8ea0d847 | 348 | if (io.flag == Comm::OK && io.size > 0) { |
434a79b0 DK |
349 | fd_bytes(io.fd, io.size, FD_READ); |
350 | } | |
351 | ||
8ea0d847 | 352 | if (io.flag != Comm::OK) { |
434a79b0 | 353 | debugs(50, ignoreErrno(io.xerrno) ? 3 : DBG_IMPORTANT, |
e7ce227f | 354 | "FTP control reply read error: " << xstrerr(io.xerrno)); |
434a79b0 DK |
355 | |
356 | if (ignoreErrno(io.xerrno)) { | |
357 | scheduleReadControlReply(0); | |
358 | } else { | |
359 | failed(ERR_READ_ERROR, io.xerrno); | |
360 | /* failed closes ctrl.conn and frees ftpState */ | |
361 | } | |
362 | return; | |
363 | } | |
364 | ||
365 | if (io.size == 0) { | |
366 | if (entry->store_status == STORE_PENDING) { | |
367 | failed(ERR_FTP_FAILURE, 0); | |
368 | /* failed closes ctrl.conn and frees ftpState */ | |
369 | return; | |
370 | } | |
371 | ||
372 | /* XXX this may end up having to be serverComplete() .. */ | |
373 | abortTransaction("zero control reply read"); | |
374 | return; | |
375 | } | |
376 | ||
377 | unsigned int len =io.size + ctrl.offset; | |
378 | ctrl.offset = len; | |
379 | assert(len <= ctrl.size); | |
5948d8e9 AR |
380 | if (Comm::IsConnOpen(ctrl.conn)) |
381 | commUnsetConnTimeout(ctrl.conn); // we are done waiting for ctrl reply | |
434a79b0 DK |
382 | handleControlReply(); |
383 | } | |
384 | ||
385 | void | |
5517260a | 386 | Ftp::Client::handleControlReply() |
434a79b0 | 387 | { |
e7ce227f | 388 | debugs(9, 3, status()); |
434a79b0 DK |
389 | |
390 | size_t bytes_used = 0; | |
391 | wordlistDestroy(&ctrl.message); | |
434a79b0 | 392 | |
a2c7f09a | 393 | if (!parseControlReply(bytes_used)) { |
434a79b0 DK |
394 | /* didn't get complete reply yet */ |
395 | ||
396 | if (ctrl.offset == ctrl.size) { | |
e7ce227f | 397 | ctrl.buf = static_cast<char*>(memReallocBuf(ctrl.buf, ctrl.size << 1, &ctrl.size)); |
434a79b0 DK |
398 | } |
399 | ||
400 | scheduleReadControlReply(0); | |
401 | return; | |
e7ce227f | 402 | } |
a2c7f09a AR |
403 | |
404 | assert(ctrl.message); // the entire FTP server response, line by line | |
405 | assert(ctrl.replycode >= 0); // FTP status code (from the last line) | |
406 | assert(ctrl.last_reply); // FTP reason (from the last line) | |
407 | ||
408 | if (ctrl.offset == bytes_used) { | |
434a79b0 DK |
409 | /* used it all up */ |
410 | ctrl.offset = 0; | |
411 | } else { | |
412 | /* Got some data past the complete reply */ | |
413 | assert(bytes_used < ctrl.offset); | |
414 | ctrl.offset -= bytes_used; | |
415 | memmove(ctrl.buf, ctrl.buf + bytes_used, ctrl.offset); | |
416 | } | |
417 | ||
e7ce227f | 418 | debugs(9, 3, "state=" << state << ", code=" << ctrl.replycode); |
434a79b0 DK |
419 | } |
420 | ||
421 | bool | |
5517260a | 422 | Ftp::Client::handlePasvReply(Ip::Address &srvAddr) |
434a79b0 DK |
423 | { |
424 | int code = ctrl.replycode; | |
434a79b0 | 425 | char *buf; |
e7ce227f | 426 | debugs(9, 3, status()); |
434a79b0 DK |
427 | |
428 | if (code != 227) { | |
429 | debugs(9, 2, "PASV not supported by remote end"); | |
430 | return false; | |
431 | } | |
432 | ||
433 | /* 227 Entering Passive Mode (h1,h2,h3,h4,p1,p2). */ | |
434 | /* ANSI sez [^0-9] is undefined, it breaks on Watcom cc */ | |
e7ce227f | 435 | debugs(9, 5, "scanning: " << ctrl.last_reply); |
434a79b0 DK |
436 | |
437 | buf = ctrl.last_reply + strcspn(ctrl.last_reply, "0123456789"); | |
438 | ||
cff221ee AR |
439 | const char *forceIp = Config.Ftp.sanitycheck ? |
440 | fd_table[ctrl.conn->fd].ipaddr : NULL; | |
73950ceb | 441 | if (!Ftp::ParseIpPort(buf, forceIp, srvAddr)) { |
434a79b0 DK |
442 | debugs(9, DBG_IMPORTANT, "Unsafe PASV reply from " << |
443 | ctrl.conn->remote << ": " << ctrl.last_reply); | |
444 | return false; | |
445 | } | |
446 | ||
73950ceb AR |
447 | data.addr(srvAddr); |
448 | ||
434a79b0 DK |
449 | return true; |
450 | } | |
451 | ||
000e664b | 452 | bool |
5517260a | 453 | Ftp::Client::handleEpsvReply(Ip::Address &remoteAddr) |
000e664b AR |
454 | { |
455 | int code = ctrl.replycode; | |
456 | char *buf; | |
e7ce227f | 457 | debugs(9, 3, status()); |
000e664b AR |
458 | |
459 | if (code != 229 && code != 522) { | |
460 | if (code == 200) { | |
461 | /* handle broken servers (RFC 2428 says OK code for EPSV MUST be 229 not 200) */ | |
462 | /* vsftpd for one send '200 EPSV ALL ok.' without even port info. | |
463 | * Its okay to re-send EPSV 1/2 but nothing else. */ | |
464 | debugs(9, DBG_IMPORTANT, "Broken FTP Server at " << ctrl.conn->remote << ". Wrong accept code for EPSV"); | |
465 | } else { | |
466 | debugs(9, 2, "EPSV not supported by remote end"); | |
467 | } | |
468 | return sendPassive(); | |
469 | } | |
470 | ||
471 | if (code == 522) { | |
e7ce227f AR |
472 | /* Peer responded with a list of supported methods: |
473 | * 522 Network protocol not supported, use (1) | |
474 | * 522 Network protocol not supported, use (1,2) | |
475 | * 522 Network protocol not supported, use (2) | |
476 | * TODO: Handle the (1,2) case which may happen after EPSV ALL. Close | |
477 | * data + control without self-destructing and re-open from scratch. | |
478 | */ | |
479 | debugs(9, 5, "scanning: " << ctrl.last_reply); | |
000e664b AR |
480 | buf = ctrl.last_reply; |
481 | while (buf != NULL && *buf != '\0' && *buf != '\n' && *buf != '(') | |
482 | ++buf; | |
483 | if (buf != NULL && *buf == '\n') | |
484 | ++buf; | |
485 | ||
486 | if (buf == NULL || *buf == '\0') { | |
487 | /* handle broken server (RFC 2428 says MUST specify supported protocols in 522) */ | |
488 | debugs(9, DBG_IMPORTANT, "Broken FTP Server at " << ctrl.conn->remote << ". 522 error missing protocol negotiation hints"); | |
489 | return sendPassive(); | |
490 | } else if (strcmp(buf, "(1)") == 0) { | |
491 | state = SENT_EPSV_2; /* simulate having sent and failed EPSV 2 */ | |
492 | return sendPassive(); | |
493 | } else if (strcmp(buf, "(2)") == 0) { | |
494 | if (Ip::EnableIpv6) { | |
495 | /* If server only supports EPSV 2 and we have already tried that. Go straight to EPRT */ | |
496 | if (state == SENT_EPSV_2) { | |
497 | return sendEprt(); | |
498 | } else { | |
499 | /* or try the next Passive mode down the chain. */ | |
500 | return sendPassive(); | |
501 | } | |
502 | } else { | |
503 | /* Server only accept EPSV in IPv6 traffic. */ | |
504 | state = SENT_EPSV_1; /* simulate having sent and failed EPSV 1 */ | |
505 | return sendPassive(); | |
506 | } | |
507 | } else { | |
508 | /* handle broken server (RFC 2428 says MUST specify supported protocols in 522) */ | |
509 | debugs(9, DBG_IMPORTANT, "WARNING: Server at " << ctrl.conn->remote << " sent unknown protocol negotiation hint: " << buf); | |
510 | return sendPassive(); | |
511 | } | |
512 | failed(ERR_FTP_FAILURE, 0); | |
513 | return false; | |
514 | } | |
515 | ||
516 | /* 229 Entering Extended Passive Mode (|||port|) */ | |
517 | /* ANSI sez [^0-9] is undefined, it breaks on Watcom cc */ | |
518 | debugs(9, 5, "scanning: " << ctrl.last_reply); | |
519 | ||
520 | buf = ctrl.last_reply + strcspn(ctrl.last_reply, "("); | |
521 | ||
522 | char h1, h2, h3, h4; | |
523 | unsigned short port; | |
524 | int n = sscanf(buf, "(%c%c%c%hu%c)", &h1, &h2, &h3, &port, &h4); | |
525 | ||
526 | if (n < 4 || h1 != h2 || h1 != h3 || h1 != h4) { | |
527 | debugs(9, DBG_IMPORTANT, "Invalid EPSV reply from " << | |
528 | ctrl.conn->remote << ": " << | |
529 | ctrl.last_reply); | |
530 | ||
531 | return sendPassive(); | |
532 | } | |
533 | ||
534 | if (0 == port) { | |
535 | debugs(9, DBG_IMPORTANT, "Unsafe EPSV reply from " << | |
536 | ctrl.conn->remote << ": " << | |
537 | ctrl.last_reply); | |
538 | ||
539 | return sendPassive(); | |
540 | } | |
541 | ||
542 | if (Config.Ftp.sanitycheck) { | |
543 | if (port < 1024) { | |
544 | debugs(9, DBG_IMPORTANT, "Unsafe EPSV reply from " << | |
545 | ctrl.conn->remote << ": " << | |
546 | ctrl.last_reply); | |
547 | ||
548 | return sendPassive(); | |
549 | } | |
550 | } | |
551 | ||
552 | remoteAddr = ctrl.conn->remote; | |
553 | remoteAddr.port(port); | |
554 | data.addr(remoteAddr); | |
555 | return true; | |
556 | } | |
557 | ||
e7ce227f | 558 | // FTP clients do not support EPRT and PORT commands yet. |
5517260a | 559 | // The Ftp::Client::sendEprt() will fail because of the unimplemented |
000e664b AR |
560 | // openListenSocket() or sendPort() methods |
561 | bool | |
5517260a | 562 | Ftp::Client::sendEprt() |
000e664b AR |
563 | { |
564 | if (!Config.Ftp.eprt) { | |
565 | /* Disabled. Switch immediately to attempting old PORT command. */ | |
566 | debugs(9, 3, "EPRT disabled by local administrator"); | |
567 | return sendPort(); | |
568 | } | |
569 | ||
e7ce227f | 570 | debugs(9, 3, status()); |
000e664b AR |
571 | |
572 | if (!openListenSocket()) { | |
573 | failed(ERR_FTP_FAILURE, 0); | |
574 | return false; | |
575 | } | |
576 | ||
577 | debugs(9, 3, "Listening for FTP data connection with FD " << data.conn); | |
578 | if (!Comm::IsConnOpen(data.conn)) { | |
e7ce227f | 579 | // TODO: Set error message. |
000e664b AR |
580 | failed(ERR_FTP_FAILURE, 0); |
581 | return false; | |
582 | } | |
583 | ||
584 | static MemBuf mb; | |
585 | mb.reset(); | |
586 | char buf[MAX_IPSTRLEN]; | |
587 | /* RFC 2428 defines EPRT as IPv6 equivalent to IPv4 PORT command. */ | |
588 | /* Which can be used by EITHER protocol. */ | |
589 | debugs(9, 3, "Listening for FTP data connection on port" << comm_local_port(data.conn->fd) << " or port?" << data.conn->local.port()); | |
590 | mb.Printf("EPRT |%d|%s|%d|%s", | |
591 | ( data.conn->local.isIPv6() ? 2 : 1 ), | |
592 | data.conn->local.toStr(buf,MAX_IPSTRLEN), | |
593 | comm_local_port(data.conn->fd), Ftp::crlf ); | |
594 | ||
595 | state = SENT_EPRT; | |
596 | writeCommand(mb.content()); | |
597 | return true; | |
598 | } | |
599 | ||
600 | bool | |
5517260a | 601 | Ftp::Client::sendPort() |
000e664b AR |
602 | { |
603 | failed(ERR_FTP_FAILURE, 0); | |
604 | return false; | |
605 | } | |
606 | ||
607 | bool | |
5517260a | 608 | Ftp::Client::sendPassive() |
000e664b | 609 | { |
e7ce227f | 610 | debugs(9, 3, status()); |
000e664b AR |
611 | |
612 | /** \par | |
613 | * Checks for EPSV ALL special conditions: | |
614 | * If enabled to be sent, squid MUST NOT request any other connect methods. | |
615 | * If 'ALL' is sent and fails the entire FTP Session fails. | |
616 | * NP: By my reading exact EPSV protocols maybe attempted, but only EPSV method. */ | |
617 | if (Config.Ftp.epsv_all && state == SENT_EPSV_1 ) { | |
618 | // We are here because the last "EPSV 1" failed, but because of epsv_all | |
619 | // no other method allowed. | |
620 | debugs(9, DBG_IMPORTANT, "FTP does not allow PASV method after 'EPSV ALL' has been sent."); | |
621 | failed(ERR_FTP_FAILURE, 0); | |
622 | return false; | |
623 | } | |
624 | ||
000e664b AR |
625 | /// Closes any old FTP-Data connection which may exist. */ |
626 | data.close(); | |
627 | ||
628 | /** \par | |
629 | * Checks for previous EPSV/PASV failures on this server/session. | |
630 | * Diverts to EPRT immediately if they are not working. */ | |
631 | if (!Config.Ftp.passive || state == SENT_PASV) { | |
632 | sendEprt(); | |
633 | return true; | |
634 | } | |
635 | ||
636 | static MemBuf mb; | |
637 | mb.reset(); | |
638 | /** \par | |
639 | * Send EPSV (ALL,2,1) or PASV on the control channel. | |
640 | * | |
641 | * - EPSV ALL is used if enabled. | |
642 | * - EPSV 2 is used if ALL is disabled and IPv6 is available and ctrl channel is IPv6. | |
643 | * - EPSV 1 is used if EPSV 2 (IPv6) fails or is not available or ctrl channel is IPv4. | |
644 | * - PASV is used if EPSV 1 fails. | |
645 | */ | |
646 | switch (state) { | |
647 | case SENT_EPSV_ALL: /* EPSV ALL resulted in a bad response. Try ther EPSV methods. */ | |
648 | if (ctrl.conn->local.isIPv6()) { | |
e7ce227f | 649 | debugs(9, 5, "FTP Channel is IPv6 (" << ctrl.conn->remote << ") attempting EPSV 2 after EPSV ALL has failed."); |
000e664b AR |
650 | mb.Printf("EPSV 2%s", Ftp::crlf); |
651 | state = SENT_EPSV_2; | |
652 | break; | |
653 | } | |
f53969cc | 654 | // else fall through to skip EPSV 2 |
000e664b | 655 | |
f8e4867b | 656 | case SENT_EPSV_2: /* EPSV IPv6 failed. Try EPSV IPv4 */ |
000e664b | 657 | if (ctrl.conn->local.isIPv4()) { |
e7ce227f | 658 | debugs(9, 5, "FTP Channel is IPv4 (" << ctrl.conn->remote << ") attempting EPSV 1 after EPSV ALL has failed."); |
000e664b AR |
659 | mb.Printf("EPSV 1%s", Ftp::crlf); |
660 | state = SENT_EPSV_1; | |
661 | break; | |
662 | } else if (Config.Ftp.epsv_all) { | |
663 | debugs(9, DBG_IMPORTANT, "FTP does not allow PASV method after 'EPSV ALL' has been sent."); | |
664 | failed(ERR_FTP_FAILURE, 0); | |
665 | return false; | |
666 | } | |
f53969cc | 667 | // else fall through to skip EPSV 1 |
000e664b AR |
668 | |
669 | case SENT_EPSV_1: /* EPSV options exhausted. Try PASV now. */ | |
e7ce227f | 670 | debugs(9, 5, "FTP Channel (" << ctrl.conn->remote << ") rejects EPSV connection attempts. Trying PASV instead."); |
000e664b AR |
671 | mb.Printf("PASV%s", Ftp::crlf); |
672 | state = SENT_PASV; | |
673 | break; | |
674 | ||
f8e4867b AR |
675 | default: { |
676 | bool doEpsv = true; | |
677 | if (Config.accessList.ftp_epsv) { | |
678 | ACLFilledChecklist checklist(Config.accessList.ftp_epsv, fwd->request, NULL); | |
679 | doEpsv = (checklist.fastCheck() == ACCESS_ALLOWED); | |
680 | } | |
681 | if (!doEpsv) { | |
e7ce227f | 682 | debugs(9, 5, "EPSV support manually disabled. Sending PASV for FTP Channel (" << ctrl.conn->remote <<")"); |
000e664b AR |
683 | mb.Printf("PASV%s", Ftp::crlf); |
684 | state = SENT_PASV; | |
685 | } else if (Config.Ftp.epsv_all) { | |
e7ce227f | 686 | debugs(9, 5, "EPSV ALL manually enabled. Attempting with FTP Channel (" << ctrl.conn->remote <<")"); |
000e664b AR |
687 | mb.Printf("EPSV ALL%s", Ftp::crlf); |
688 | state = SENT_EPSV_ALL; | |
689 | } else { | |
690 | if (ctrl.conn->local.isIPv6()) { | |
e7ce227f | 691 | debugs(9, 5, "FTP Channel (" << ctrl.conn->remote << "). Sending default EPSV 2"); |
000e664b AR |
692 | mb.Printf("EPSV 2%s", Ftp::crlf); |
693 | state = SENT_EPSV_2; | |
694 | } | |
695 | if (ctrl.conn->local.isIPv4()) { | |
e7ce227f | 696 | debugs(9, 5, "Channel (" << ctrl.conn->remote <<"). Sending default EPSV 1"); |
000e664b AR |
697 | mb.Printf("EPSV 1%s", Ftp::crlf); |
698 | state = SENT_EPSV_1; | |
699 | } | |
700 | } | |
701 | break; | |
702 | } | |
e7ce227f | 703 | } |
000e664b AR |
704 | |
705 | if (ctrl.message) | |
706 | wordlistDestroy(&ctrl.message); | |
707 | ctrl.message = NULL; //No message to return to client. | |
708 | ctrl.offset = 0; //reset readed response, to make room read the next response | |
709 | ||
710 | writeCommand(mb.content()); | |
000e664b | 711 | |
e7ce227f | 712 | shortenReadTimeout = true; |
f8e4867b | 713 | return true; |
000e664b AR |
714 | } |
715 | ||
434a79b0 | 716 | void |
5517260a | 717 | Ftp::Client::connectDataChannel() |
434a79b0 DK |
718 | { |
719 | safe_free(ctrl.last_command); | |
720 | ||
721 | safe_free(ctrl.last_reply); | |
722 | ||
723 | ctrl.last_command = xstrdup("Connect to server data port"); | |
724 | ||
73950ceb | 725 | // Generate a new data channel descriptor to be opened. |
434a79b0 | 726 | Comm::ConnectionPointer conn = new Comm::Connection; |
8ea0d847 | 727 | conn->setAddrs(ctrl.conn->local, data.host); |
73950ceb | 728 | conn->local.port(0); |
73950ceb | 729 | conn->remote.port(data.port); |
8ea0d847 AR |
730 | conn->tos = ctrl.conn->tos; |
731 | conn->nfmark = ctrl.conn->nfmark; | |
434a79b0 | 732 | |
e7ce227f | 733 | debugs(9, 3, "connecting to " << conn->remote); |
434a79b0 | 734 | |
43446566 AR |
735 | typedef CommCbMemFunT<Client, CommConnectCbParams> Dialer; |
736 | data.opener = JobCallback(9, 3, Dialer, this, Ftp::Client::dataChannelConnected); | |
434a79b0 | 737 | Comm::ConnOpener *cs = new Comm::ConnOpener(conn, data.opener, Config.Timeout.connect); |
73950ceb | 738 | cs->setHost(data.host); |
434a79b0 DK |
739 | AsyncJob::Start(cs); |
740 | } | |
741 | ||
000e664b | 742 | bool |
5517260a | 743 | Ftp::Client::openListenSocket() |
000e664b AR |
744 | { |
745 | return false; | |
746 | } | |
747 | ||
434a79b0 DK |
748 | /// creates a data channel Comm close callback |
749 | AsyncCall::Pointer | |
5517260a | 750 | Ftp::Client::dataCloser() |
434a79b0 | 751 | { |
5517260a AR |
752 | typedef CommCbMemFunT<Client, CommCloseCbParams> Dialer; |
753 | return JobCallback(9, 5, Dialer, this, Ftp::Client::dataClosed); | |
434a79b0 DK |
754 | } |
755 | ||
756 | /// handler called by Comm when FTP data channel is closed unexpectedly | |
757 | void | |
5517260a | 758 | Ftp::Client::dataClosed(const CommCloseCbParams &io) |
434a79b0 | 759 | { |
e7ce227f | 760 | debugs(9, 4, status()); |
434a79b0 DK |
761 | if (data.listenConn != NULL) { |
762 | data.listenConn->close(); | |
763 | data.listenConn = NULL; | |
764 | // NP clear() does the: data.fd = -1; | |
765 | } | |
766 | data.clear(); | |
767 | } | |
768 | ||
769 | void | |
5517260a | 770 | Ftp::Client::writeCommand(const char *buf) |
434a79b0 DK |
771 | { |
772 | char *ebuf; | |
773 | /* trace FTP protocol communications at level 2 */ | |
774 | debugs(9, 2, "ftp<< " << buf); | |
775 | ||
776 | if (Config.Ftp.telnet) | |
777 | ebuf = escapeIAC(buf); | |
778 | else | |
779 | ebuf = xstrdup(buf); | |
780 | ||
781 | safe_free(ctrl.last_command); | |
782 | ||
783 | safe_free(ctrl.last_reply); | |
784 | ||
785 | ctrl.last_command = ebuf; | |
786 | ||
787 | if (!Comm::IsConnOpen(ctrl.conn)) { | |
e7ce227f | 788 | debugs(9, 2, "cannot send to closing ctrl " << ctrl.conn); |
434a79b0 DK |
789 | // TODO: assert(ctrl.closer != NULL); |
790 | return; | |
791 | } | |
792 | ||
5517260a | 793 | typedef CommCbMemFunT<Client, CommIoCbParams> Dialer; |
434a79b0 | 794 | AsyncCall::Pointer call = JobCallback(9, 5, Dialer, this, |
5517260a | 795 | Ftp::Client::writeCommandCallback); |
434a79b0 DK |
796 | Comm::Write(ctrl.conn, ctrl.last_command, strlen(ctrl.last_command), call, NULL); |
797 | ||
798 | scheduleReadControlReply(0); | |
799 | } | |
800 | ||
801 | void | |
5517260a | 802 | Ftp::Client::writeCommandCallback(const CommIoCbParams &io) |
434a79b0 DK |
803 | { |
804 | ||
e7ce227f | 805 | debugs(9, 5, "wrote " << io.size << " bytes"); |
434a79b0 DK |
806 | |
807 | if (io.size > 0) { | |
808 | fd_bytes(io.fd, io.size, FD_WRITE); | |
809 | kb_incr(&(statCounter.server.all.kbytes_out), io.size); | |
810 | kb_incr(&(statCounter.server.ftp.kbytes_out), io.size); | |
811 | } | |
812 | ||
8ea0d847 | 813 | if (io.flag == Comm::ERR_CLOSING) |
434a79b0 DK |
814 | return; |
815 | ||
816 | if (io.flag) { | |
e7ce227f | 817 | debugs(9, DBG_IMPORTANT, "FTP command write error: " << io.conn << ": " << xstrerr(io.xerrno)); |
434a79b0 DK |
818 | failed(ERR_WRITE_ERROR, io.xerrno); |
819 | /* failed closes ctrl.conn and frees ftpState */ | |
820 | return; | |
821 | } | |
822 | } | |
823 | ||
824 | /// handler called by Comm when FTP control channel is closed unexpectedly | |
825 | void | |
5517260a | 826 | Ftp::Client::ctrlClosed(const CommCloseCbParams &io) |
434a79b0 | 827 | { |
e7ce227f | 828 | debugs(9, 4, status()); |
434a79b0 | 829 | ctrl.clear(); |
5517260a | 830 | mustStop("Ftp::Client::ctrlClosed"); |
434a79b0 DK |
831 | } |
832 | ||
833 | void | |
5517260a | 834 | Ftp::Client::timeout(const CommTimeoutCbParams &io) |
434a79b0 | 835 | { |
e7ce227f | 836 | debugs(9, 4, io.conn << ": '" << entry->url() << "'" ); |
434a79b0 DK |
837 | |
838 | if (abortOnBadEntry("entry went bad while waiting for a timeout")) | |
839 | return; | |
840 | ||
841 | failed(ERR_READ_TIMEOUT, 0); | |
842 | /* failed() closes ctrl.conn and frees ftpState */ | |
843 | } | |
844 | ||
845 | const Comm::ConnectionPointer & | |
5517260a | 846 | Ftp::Client::dataConnection() const |
434a79b0 DK |
847 | { |
848 | return data.conn; | |
849 | } | |
850 | ||
851 | void | |
5517260a | 852 | Ftp::Client::maybeReadVirginBody() |
434a79b0 DK |
853 | { |
854 | // too late to read | |
855 | if (!Comm::IsConnOpen(data.conn) || fd_table[data.conn->fd].closing()) | |
856 | return; | |
857 | ||
858 | if (data.read_pending) | |
859 | return; | |
860 | ||
adaff124 DK |
861 | initReadBuf(); |
862 | ||
434a79b0 DK |
863 | const int read_sz = replyBodySpace(*data.readBuf, 0); |
864 | ||
aea65fec | 865 | debugs(9, 9, "FTP may read up to " << read_sz << " bytes"); |
434a79b0 | 866 | |
e7ce227f | 867 | if (read_sz < 2) // see http.cc |
434a79b0 DK |
868 | return; |
869 | ||
870 | data.read_pending = true; | |
871 | ||
5517260a | 872 | typedef CommCbMemFunT<Client, CommTimeoutCbParams> TimeoutDialer; |
434a79b0 | 873 | AsyncCall::Pointer timeoutCall = JobCallback(9, 5, |
5517260a | 874 | TimeoutDialer, this, Ftp::Client::timeout); |
434a79b0 DK |
875 | commSetConnTimeout(data.conn, Config.Timeout.read, timeoutCall); |
876 | ||
e7ce227f | 877 | debugs(9,5,"queueing read on FD " << data.conn->fd); |
434a79b0 | 878 | |
5517260a | 879 | typedef CommCbMemFunT<Client, CommIoCbParams> Dialer; |
434a79b0 | 880 | entry->delayAwareRead(data.conn, data.readBuf->space(), read_sz, |
5517260a | 881 | JobCallback(9, 5, Dialer, this, Ftp::Client::dataRead)); |
434a79b0 DK |
882 | } |
883 | ||
884 | void | |
5517260a | 885 | Ftp::Client::dataRead(const CommIoCbParams &io) |
434a79b0 DK |
886 | { |
887 | int j; | |
888 | int bin; | |
889 | ||
890 | data.read_pending = false; | |
891 | ||
e7ce227f | 892 | debugs(9, 3, "FD " << io.fd << " Read " << io.size << " bytes"); |
434a79b0 DK |
893 | |
894 | if (io.size > 0) { | |
895 | kb_incr(&(statCounter.server.all.kbytes_in), io.size); | |
896 | kb_incr(&(statCounter.server.ftp.kbytes_in), io.size); | |
897 | } | |
898 | ||
8ea0d847 | 899 | if (io.flag == Comm::ERR_CLOSING) |
434a79b0 DK |
900 | return; |
901 | ||
902 | assert(io.fd == data.conn->fd); | |
903 | ||
904 | if (EBIT_TEST(entry->flags, ENTRY_ABORTED)) { | |
905 | abortTransaction("entry aborted during dataRead"); | |
906 | return; | |
907 | } | |
908 | ||
8ea0d847 AR |
909 | if (io.flag == Comm::OK && io.size > 0) { |
910 | debugs(9, 5, "appended " << io.size << " bytes to readBuf"); | |
434a79b0 DK |
911 | data.readBuf->appended(io.size); |
912 | #if USE_DELAY_POOLS | |
913 | DelayId delayId = entry->mem_obj->mostBytesAllowed(); | |
914 | delayId.bytesIn(io.size); | |
915 | #endif | |
916 | ++ IOStats.Ftp.reads; | |
917 | ||
918 | for (j = io.size - 1, bin = 0; j; ++bin) | |
919 | j >>= 1; | |
920 | ||
921 | ++ IOStats.Ftp.read_hist[bin]; | |
922 | } | |
923 | ||
8ea0d847 | 924 | if (io.flag != Comm::OK) { |
434a79b0 | 925 | debugs(50, ignoreErrno(io.xerrno) ? 3 : DBG_IMPORTANT, |
e7ce227f | 926 | "FTP data read error: " << xstrerr(io.xerrno)); |
434a79b0 DK |
927 | |
928 | if (ignoreErrno(io.xerrno)) { | |
434a79b0 DK |
929 | maybeReadVirginBody(); |
930 | } else { | |
931 | failed(ERR_READ_ERROR, 0); | |
932 | /* failed closes ctrl.conn and frees ftpState */ | |
933 | return; | |
934 | } | |
935 | } else if (io.size == 0) { | |
e7ce227f | 936 | debugs(9, 3, "Calling dataComplete() because io.size == 0"); |
434a79b0 DK |
937 | /* |
938 | * DPW 2007-04-23 | |
939 | * Dangerous curves ahead. This call to dataComplete was | |
940 | * calling scheduleReadControlReply, handleControlReply, | |
941 | * and then ftpReadTransferDone. If ftpReadTransferDone | |
942 | * gets unexpected status code, it closes down the control | |
943 | * socket and our FtpStateData object gets destroyed. As | |
944 | * a workaround we no longer set the 'buffered_ok' flag in | |
945 | * the scheduleReadControlReply call. | |
946 | */ | |
947 | dataComplete(); | |
948 | } | |
949 | ||
950 | processReplyBody(); | |
951 | } | |
952 | ||
953 | void | |
5517260a | 954 | Ftp::Client::dataComplete() |
434a79b0 | 955 | { |
e7ce227f | 956 | debugs(9, 3,status()); |
434a79b0 DK |
957 | |
958 | /* Connection closed; transfer done. */ | |
959 | ||
960 | /// Close data channel, if any, to conserve resources while we wait. | |
961 | data.close(); | |
962 | ||
963 | /* expect the "transfer complete" message on the control socket */ | |
964 | /* | |
965 | * DPW 2007-04-23 | |
966 | * Previously, this was the only place where we set the | |
967 | * 'buffered_ok' flag when calling scheduleReadControlReply(). | |
968 | * It caused some problems if the FTP server returns an unexpected | |
969 | * status code after the data command. FtpStateData was being | |
970 | * deleted in the middle of dataRead(). | |
971 | */ | |
972 | /* AYJ: 2011-01-13: Bug 2581. | |
973 | * 226 status is possibly waiting in the ctrl buffer. | |
974 | * The connection will hang if we DONT send buffered_ok. | |
975 | * This happens on all transfers which can be completly sent by the | |
976 | * server before the 150 started status message is read in by Squid. | |
977 | * ie all transfers of about one packet hang. | |
978 | */ | |
979 | scheduleReadControlReply(1); | |
980 | } | |
981 | ||
982 | /** | |
983 | * Quickly abort the transaction | |
984 | * | |
985 | \todo destruction should be sufficient as the destructor should cleanup, | |
e7ce227f | 986 | * including canceling close handlers |
434a79b0 DK |
987 | */ |
988 | void | |
5517260a | 989 | Ftp::Client::abortTransaction(const char *reason) |
434a79b0 | 990 | { |
e7ce227f | 991 | debugs(9, 3, "aborting transaction for " << reason << |
434a79b0 DK |
992 | "; FD " << (ctrl.conn!=NULL?ctrl.conn->fd:-1) << ", Data FD " << (data.conn!=NULL?data.conn->fd:-1) << ", this " << this); |
993 | if (Comm::IsConnOpen(ctrl.conn)) { | |
994 | ctrl.conn->close(); | |
995 | return; | |
996 | } | |
997 | ||
998 | fwd->handleUnregisteredServerEnd(); | |
5517260a | 999 | mustStop("Ftp::Client::abortTransaction"); |
434a79b0 DK |
1000 | } |
1001 | ||
1002 | /** | |
1003 | * Cancel the timeout on the Control socket and establish one | |
1004 | * on the data socket | |
1005 | */ | |
1006 | void | |
5517260a | 1007 | Ftp::Client::switchTimeoutToDataChannel() |
434a79b0 DK |
1008 | { |
1009 | commUnsetConnTimeout(ctrl.conn); | |
1010 | ||
5517260a | 1011 | typedef CommCbMemFunT<Client, CommTimeoutCbParams> TimeoutDialer; |
434a79b0 | 1012 | AsyncCall::Pointer timeoutCall = JobCallback(9, 5, TimeoutDialer, this, |
27c841f6 | 1013 | Ftp::Client::timeout); |
434a79b0 DK |
1014 | commSetConnTimeout(data.conn, Config.Timeout.read, timeoutCall); |
1015 | } | |
1016 | ||
1017 | void | |
5517260a | 1018 | Ftp::Client::sentRequestBody(const CommIoCbParams &io) |
434a79b0 DK |
1019 | { |
1020 | if (io.size > 0) | |
1021 | kb_incr(&(statCounter.server.ftp.kbytes_out), io.size); | |
fccd4a86 | 1022 | ::Client::sentRequestBody(io); |
434a79b0 DK |
1023 | } |
1024 | ||
1025 | /** | |
1026 | * called after we wrote the last byte of the request body | |
1027 | */ | |
1028 | void | |
5517260a | 1029 | Ftp::Client::doneSendingRequestBody() |
434a79b0 | 1030 | { |
fccd4a86 | 1031 | ::Client::doneSendingRequestBody(); |
e7ce227f | 1032 | debugs(9, 3, status()); |
434a79b0 DK |
1033 | dataComplete(); |
1034 | /* NP: RFC 959 3.3. DATA CONNECTION MANAGEMENT | |
1035 | * if transfer type is 'stream' call dataComplete() | |
1036 | * otherwise leave open. (reschedule control channel read?) | |
1037 | */ | |
1038 | } | |
1039 | ||
a2c7f09a AR |
1040 | /// Parses FTP server control response into ctrl structure fields, |
1041 | /// setting bytesUsed and returning true on success. | |
1042 | bool | |
5517260a | 1043 | Ftp::Client::parseControlReply(size_t &bytesUsed) |
434a79b0 DK |
1044 | { |
1045 | char *s; | |
1046 | char *sbuf; | |
1047 | char *end; | |
1048 | int usable; | |
1049 | int complete = 0; | |
1050 | wordlist *head = NULL; | |
1051 | wordlist *list; | |
1052 | wordlist **tail = &head; | |
434a79b0 | 1053 | size_t linelen; |
e7ce227f | 1054 | debugs(9, 3, status()); |
434a79b0 DK |
1055 | /* |
1056 | * We need a NULL-terminated buffer for scanning, ick | |
1057 | */ | |
a2c7f09a | 1058 | const size_t len = ctrl.offset; |
434a79b0 | 1059 | sbuf = (char *)xmalloc(len + 1); |
a2c7f09a | 1060 | xstrncpy(sbuf, ctrl.buf, len + 1); |
434a79b0 DK |
1061 | end = sbuf + len - 1; |
1062 | ||
1063 | while (*end != '\r' && *end != '\n' && end > sbuf) | |
1064 | --end; | |
1065 | ||
1066 | usable = end - sbuf; | |
1067 | ||
e7ce227f | 1068 | debugs(9, 3, "usable = " << usable); |
434a79b0 DK |
1069 | |
1070 | if (usable == 0) { | |
e7ce227f | 1071 | debugs(9, 3, "didn't find end of line"); |
434a79b0 | 1072 | safe_free(sbuf); |
a2c7f09a | 1073 | return false; |
434a79b0 DK |
1074 | } |
1075 | ||
e7ce227f | 1076 | debugs(9, 3, len << " bytes to play with"); |
434a79b0 DK |
1077 | ++end; |
1078 | s = sbuf; | |
1079 | s += strspn(s, crlf); | |
1080 | ||
1081 | for (; s < end; s += strcspn(s, crlf), s += strspn(s, crlf)) { | |
1082 | if (complete) | |
1083 | break; | |
1084 | ||
e7ce227f | 1085 | debugs(9, 5, "s = {" << s << "}"); |
434a79b0 DK |
1086 | |
1087 | linelen = strcspn(s, crlf) + 1; | |
1088 | ||
1089 | if (linelen < 2) | |
1090 | break; | |
1091 | ||
1092 | if (linelen > 3) | |
1093 | complete = (*s >= '0' && *s <= '9' && *(s + 3) == ' '); | |
1094 | ||
434a79b0 DK |
1095 | list = new wordlist(); |
1096 | ||
a2c7f09a | 1097 | list->key = (char *)xmalloc(linelen); |
434a79b0 | 1098 | |
a2c7f09a | 1099 | xstrncpy(list->key, s, linelen); |
434a79b0 DK |
1100 | |
1101 | /* trace the FTP communication chat at level 2 */ | |
a2c7f09a AR |
1102 | debugs(9, 2, "ftp>> " << list->key); |
1103 | ||
1104 | if (complete) { | |
1105 | // use list->key for last_reply because s contains the new line | |
1106 | ctrl.last_reply = xstrdup(list->key + 4); | |
1107 | ctrl.replycode = atoi(list->key); | |
1108 | } | |
434a79b0 DK |
1109 | |
1110 | *tail = list; | |
1111 | ||
1112 | tail = &list->next; | |
1113 | } | |
1114 | ||
a2c7f09a | 1115 | bytesUsed = static_cast<size_t>(s - sbuf); |
434a79b0 DK |
1116 | safe_free(sbuf); |
1117 | ||
a2c7f09a | 1118 | if (!complete) { |
434a79b0 | 1119 | wordlistDestroy(&head); |
a2c7f09a AR |
1120 | return false; |
1121 | } | |
434a79b0 | 1122 | |
a2c7f09a AR |
1123 | ctrl.message = head; |
1124 | assert(ctrl.replycode >= 0); | |
1125 | assert(ctrl.last_reply); | |
1126 | assert(ctrl.message); | |
1127 | return true; | |
434a79b0 DK |
1128 | } |
1129 | ||
1130 | }; // namespace Ftp | |
f53969cc | 1131 |