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