]> git.ipfire.org Git - thirdparty/squid.git/blame - src/ftp.cc
Fix xstrto.h stdbool wrapping
[thirdparty/squid.git] / src / ftp.cc
CommitLineData
30a4f2a8 1/*
262a0e14 2 * $Id$
30a4f2a8 3 *
b510f3a1 4 * DEBUG: section 09 File Transfer Protocol (FTP)
30a4f2a8 5 * AUTHOR: Harvest Derived
6 *
2b6662ba 7 * SQUID Web Proxy Cache http://www.squid-cache.org/
e25c139f 8 * ----------------------------------------------------------
30a4f2a8 9 *
2b6662ba 10 * Squid is the result of efforts by numerous individuals from
11 * the Internet community; see the CONTRIBUTORS file for full
12 * details. Many organizations have provided support for Squid's
13 * development; see the SPONSORS file for full details. Squid is
14 * Copyrighted (C) 2001 by the Regents of the University of
15 * California; see the COPYRIGHT file for full details. Squid
16 * incorporates software developed and/or copyrighted by other
17 * sources; see the CREDITS file for full details.
30a4f2a8 18 *
19 * This program is free software; you can redistribute it and/or modify
20 * it under the terms of the GNU General Public License as published by
21 * the Free Software Foundation; either version 2 of the License, or
22 * (at your option) any later version.
9e008dda 23 *
30a4f2a8 24 * This program is distributed in the hope that it will be useful,
25 * but WITHOUT ANY WARRANTY; without even the implied warranty of
26 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
27 * GNU General Public License for more details.
9e008dda 28 *
30a4f2a8 29 * You should have received a copy of the GNU General Public License
30 * along with this program; if not, write to the Free Software
cbdec147 31 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111, USA.
e25c139f 32 *
30a4f2a8 33 */
019dd986 34
44a47c6e 35#include "squid.h"
04f55905
AJ
36#include "comm.h"
37#include "comm/ListenStateData.h"
27bc2077 38#include "compat/strtoll.h"
04f55905 39#include "ConnectionDetail.h"
aa839030 40#include "errorpage.h"
528b2c61 41#include "fde.h"
04f55905 42#include "forward.h"
528b2c61 43#include "HttpHdrContRange.h"
04f55905 44#include "HttpHeaderRange.h"
528b2c61 45#include "HttpHeader.h"
04f55905
AJ
46#include "HttpRequest.h"
47#include "HttpReply.h"
055421ee 48#include "ip/tools.h"
04f55905 49#include "MemBuf.h"
056b24a1 50#include "rfc1738.h"
04f55905 51#include "Server.h"
056b24a1 52#include "SquidString.h"
04f55905
AJ
53#include "SquidTime.h"
54#include "Store.h"
55#include "URLScheme.h"
56#include "wordlist.h"
57
b67e2c8c 58#if DELAY_POOLS
59#include "DelayPools.h"
86a2f789 60#include "MemObject.h"
b67e2c8c 61#endif
253caccb 62
63be0a78 63/**
64 \defgroup ServerProtocolFTPInternal Server-Side FTP Internals
65 \ingroup ServerProtocolFTPAPI
66 */
67
68/// \ingroup ServerProtocolFTPInternal
3fdadc70 69static const char *const crlf = "\r\n";
63be0a78 70
71/// \ingroup ServerProtocolFTPInternal
3fdadc70 72static char cbuf[1024];
73
63be0a78 74/// \ingroup ServerProtocolFTPInternal
3fdadc70 75typedef enum {
76 BEGIN,
77 SENT_USER,
78 SENT_PASS,
79 SENT_TYPE,
80 SENT_MDTM,
81 SENT_SIZE,
cc192b50 82 SENT_EPRT,
3fdadc70 83 SENT_PORT,
a689bd4e 84 SENT_EPSV_ALL,
85 SENT_EPSV_1,
86 SENT_EPSV_2,
3fdadc70 87 SENT_PASV,
88 SENT_CWD,
89 SENT_LIST,
90 SENT_NLST,
91 SENT_REST,
92 SENT_RETR,
54220df8 93 SENT_STOR,
3fdadc70 94 SENT_QUIT,
54220df8 95 READING_DATA,
96 WRITING_DATA,
97 SENT_MKDIR
3fdadc70 98} ftp_state_t;
090089c4 99
63be0a78 100/// \ingroup ServerProtocolFTPInternal
9e008dda 101struct _ftp_flags {
4e2da309
AJ
102
103 /* passive mode */
104 bool pasv_supported; ///< PASV command is allowed
105 bool epsv_all_sent; ///< EPSV ALL has been used. Must abort on failures.
106 bool pasv_only;
107
108 /* authentication */
109 bool authenticated; ///< authentication success
110 bool tried_auth_anonymous; ///< auth has tried to use anonymous credentials already.
111 bool tried_auth_nopass; ///< auth tried username with no password already.
112
113 /* other */
3977372c 114 bool isdir;
3977372c 115 bool skip_whitespace;
116 bool rest_supported;
3977372c 117 bool http_header_sent;
118 bool tried_nlst;
119 bool need_base_href;
120 bool dir_slash;
121 bool root_dir;
122 bool no_dotdot;
3977372c 123 bool binary;
124 bool try_slash_hack;
125 bool put;
126 bool put_mkdir;
127 bool listformat_unknown;
0477a072 128 bool listing;
5f8252d2 129 bool completed_forwarding;
e55f0142 130};
131
6f0aab86 132class FtpStateData;
63be0a78 133
134/// \ingroup ServerProtocolFTPInternal
6f0aab86 135typedef void (FTPSM) (FtpStateData *);
136
94b88585 137/// common code for FTP control and data channels
6e92b048 138// does not own the channel descriptor, which is managed by FtpStateData
9e008dda
AJ
139class FtpChannel
140{
94b88585
AR
141public:
142 FtpChannel(): fd(-1) {}
143
144 /// called after the socket is opened, sets up close handler
145 void opened(int aFd, const AsyncCall::Pointer &aCloser);
146
04f55905
AJ
147 /** Handles all operations needed to properly close the active channel FD.
148 * clearing the close handler, clearing the listen socket properly, and calling comm_close
149 */
150 void close();
151
152 void clear(); /// just resets fd and close handler. does not close active connections.
94b88585
AR
153
154 int fd; /// channel descriptor; \todo: remove because the closer has it
155
04f55905
AJ
156 /** Current listening socket handler. delete on shutdown or abort.
157 * FTP stores a copy of the FD in the field fd above.
158 * Use close() to properly close the channel.
159 */
160 Comm::ListenStateData *listener;
161
94b88585
AR
162private:
163 AsyncCall::Pointer closer; /// Comm close handler callback
164};
165
63be0a78 166/// \ingroup ServerProtocolFTPInternal
253caccb 167class FtpStateData : public ServerStateData
62e76326 168{
0353e724 169
170public:
171 void *operator new (size_t);
172 void operator delete (void *);
dc56a9b1 173 void *toCbdata() { return this; }
174
6f0aab86 175 FtpStateData(FwdState *);
0353e724 176 ~FtpStateData();
77a30ebb 177 char user[MAX_URL];
178 char password[MAX_URL];
9bc73deb 179 int password_url;
f0afe435 180 char *reply_hdr;
f0afe435 181 int reply_hdr_state;
30abd221 182 String clean_url;
183 String title_url;
184 String base_href;
3fdadc70 185 int conn_att;
186 int login_att;
187 ftp_state_t state;
3fdadc70 188 time_t mdtm;
47f6e231 189 int64_t theSize;
3fdadc70 190 wordlist *pathcomps;
191 char *filepath;
5627a633 192 char *dirpath;
47f6e231 193 int64_t restart_offset;
3fdadc70 194 char *proxy_host;
195 size_t list_width;
0477a072 196 String cwd_message;
969c39b9 197 char *old_request;
198 char *old_reply;
0f169992 199 char *old_filepath;
9e242e02 200 char typecode;
0477a072 201 MemBuf listing; ///< FTP directory listing in HTML format.
62e76326 202
94b88585
AR
203 // \todo: optimize ctrl and data structs member order, to minimize size
204 /// FTP control channel info; the channel is opened once per transaction
9e008dda 205 struct CtrlChannel: public FtpChannel {
62e76326 206 char *buf;
207 size_t size;
47f6e231 208 size_t offset;
62e76326 209 wordlist *message;
210 char *last_command;
211 char *last_reply;
212 int replycode;
2fadd50d 213 } ctrl;
62e76326 214
94b88585 215 /// FTP data channel info; the channel may be opened/closed a few times
9e008dda 216 struct DataChannel: public FtpChannel {
253caccb 217 MemBuf *readBuf;
62e76326 218 char *host;
219 u_short port;
253caccb 220 bool read_pending;
2fadd50d 221 } data;
62e76326 222
e55f0142 223 struct _ftp_flags flags;
0353e724 224
225private:
226 CBDATA_CLASS(FtpStateData);
6f0aab86 227
228public:
253caccb 229 // these should all be private
6f0aab86 230 void start();
231 void loginParser(const char *, int escaped);
232 int restartable();
233 void appendSuccessHeader();
234 void hackShortcut(FTPSM * nextState);
235 void failed(err_type, int xerrno);
236 void failedErrorMessage(err_type, int xerrno);
237 void unhack();
6f0aab86 238 void scheduleReadControlReply(int);
239 void handleControlReply();
5f8252d2 240 void readStor();
f1a83a7d 241 void parseListing();
0477a072
AJ
242 MemBuf *htmlifyListEntry(const char *line);
243 void completedListing(void);
f1a83a7d 244 void dataComplete();
dc56a9b1 245 void dataRead(const CommIoCbParams &io);
f1a83a7d 246 int checkAuth(const HttpHeader * req_hdr);
247 void checkUrlpath();
248 void buildTitleUrl();
47f6e231 249 void writeReplyBody(const char *, size_t len);
253caccb 250 void printfReplyBody(const char *fmt, ...);
5f8252d2 251 virtual int dataDescriptor() const;
252 virtual void maybeReadVirginBody();
253 virtual void closeServer();
254 virtual void completeForwarding();
255 virtual void abortTransaction(const char *reason);
063a47b5 256 void processHeadResponse();
253caccb 257 void processReplyBody();
9317b41a 258 void writeCommand(const char *buf);
9fa2e208
CT
259 void setCurrentOffset(int64_t offset) { currentOffset = offset; }
260 int64_t getCurrentOffset() const { return currentOffset; }
6f0aab86 261
6f0aab86 262 static CNCB ftpPasvCallback;
6f0aab86 263 static PF ftpDataWrite;
dc56a9b1 264 void ftpTimeout(const CommTimeoutCbParams &io);
94b88585
AR
265 void ctrlClosed(const CommCloseCbParams &io);
266 void dataClosed(const CommCloseCbParams &io);
dc56a9b1 267 void ftpReadControlReply(const CommIoCbParams &io);
268 void ftpWriteCommandCallback(const CommIoCbParams &io);
269 void ftpAcceptDataConnection(const CommAcceptCbParams &io);
270
6f0aab86 271 static HttpReply *ftpAuthRequired(HttpRequest * request, const char *realm);
f381d699
AJ
272 const char *ftpRealm(void);
273 void loginFailed(void);
47f6e231 274 static wordlist *ftpParseControlReply(char *, size_t, int *, size_t *);
253caccb 275
5f8252d2 276 // sending of the request body to the server
dc56a9b1 277 virtual void sentRequestBody(const CommIoCbParams&);
5f8252d2 278 virtual void doneSendingRequestBody();
279
063a47b5 280 virtual void haveParsedReplyHeaders();
281
5f8252d2 282 virtual bool doneWithServer() const;
aa190430 283 virtual bool haveControlChannel(const char *caller_name) const;
94b88585 284 AsyncCall::Pointer dataCloser(); /// creates a Comm close callback
253caccb 285
5f8252d2 286private:
287 // BodyConsumer for HTTP: consume request body.
288 virtual void handleRequestBodyProducerAborted();
0353e724 289};
290
291CBDATA_CLASS_INIT(FtpStateData);
292
293void *
294FtpStateData::operator new (size_t)
295{
296 CBDATA_INIT_TYPE(FtpStateData);
297 FtpStateData *result = cbdataAlloc(FtpStateData);
298 return result;
62e76326 299}
090089c4 300
0353e724 301void
302FtpStateData::operator delete (void *address)
303{
304 FtpStateData *t = static_cast<FtpStateData *>(address);
305 cbdataFree(t);
306}
307
63be0a78 308/// \ingroup ServerProtocolFTPInternal
9e008dda 309typedef struct {
3fdadc70 310 char type;
47f6e231 311 int64_t size;
3fdadc70 312 char *date;
313 char *name;
314 char *showname;
315 char *link;
2fadd50d 316} ftpListParts;
3fdadc70 317
63be0a78 318/// \ingroup ServerProtocolFTPInternal
a689bd4e 319#define FTP_LOGIN_ESCAPED 1
63be0a78 320
321/// \ingroup ServerProtocolFTPInternal
a689bd4e 322#define FTP_LOGIN_NOT_ESCAPED 0
c68e9c6b 323
7e3ce7b9 324/*
325 * State machine functions
969c39b9 326 * send == state transition
327 * read == wait for response, and select next state transition
dbfed404 328 * other == Transition logic
969c39b9 329 */
3fdadc70 330static FTPSM ftpReadWelcome;
969c39b9 331static FTPSM ftpSendUser;
3fdadc70 332static FTPSM ftpReadUser;
969c39b9 333static FTPSM ftpSendPass;
3fdadc70 334static FTPSM ftpReadPass;
969c39b9 335static FTPSM ftpSendType;
3fdadc70 336static FTPSM ftpReadType;
969c39b9 337static FTPSM ftpSendMdtm;
3fdadc70 338static FTPSM ftpReadMdtm;
969c39b9 339static FTPSM ftpSendSize;
3fdadc70 340static FTPSM ftpReadSize;
cc192b50 341static FTPSM ftpSendEPRT;
342static FTPSM ftpReadEPRT;
343static FTPSM ftpSendPORT;
344static FTPSM ftpReadPORT;
a689bd4e 345static FTPSM ftpSendPassive;
cc192b50 346static FTPSM ftpReadEPSV;
3fdadc70 347static FTPSM ftpReadPasv;
dbfed404 348static FTPSM ftpTraverseDirectory;
349static FTPSM ftpListDir;
350static FTPSM ftpGetFile;
969c39b9 351static FTPSM ftpSendCwd;
3fdadc70 352static FTPSM ftpReadCwd;
94439e4e 353static FTPSM ftpRestOrList;
969c39b9 354static FTPSM ftpSendList;
355static FTPSM ftpSendNlst;
3fdadc70 356static FTPSM ftpReadList;
969c39b9 357static FTPSM ftpSendRest;
3fdadc70 358static FTPSM ftpReadRest;
969c39b9 359static FTPSM ftpSendRetr;
3fdadc70 360static FTPSM ftpReadRetr;
361static FTPSM ftpReadTransferDone;
54220df8 362static FTPSM ftpSendStor;
363static FTPSM ftpReadStor;
94439e4e 364static FTPSM ftpWriteTransferDone;
54220df8 365static FTPSM ftpSendReply;
94439e4e 366static FTPSM ftpSendMkdir;
54220df8 367static FTPSM ftpReadMkdir;
94439e4e 368static FTPSM ftpFail;
369static FTPSM ftpSendQuit;
370static FTPSM ftpReadQuit;
a689bd4e 371
372/************************************************
373** Debugs Levels used here **
374*************************************************
3750 CRITICAL Events
3761 IMPORTANT Events
377 Protocol and Transmission failures.
3782 FTP Protocol Chatter
3793 Logic Flows
3804 Data Parsing Flows
3815 Data Dumps
3827 ??
383************************************************/
384
dbfed404 385/************************************************
386** State Machine Description (excluding hacks) **
387*************************************************
388From To
389---------------------------------------
390Welcome User
391User Pass
392Pass Type
393Type TraverseDirectory / GetFile
394TraverseDirectory Cwd / GetFile / ListDir
94439e4e 395Cwd TraverseDirectory / Mkdir
dbfed404 396GetFile Mdtm
397Mdtm Size
a689bd4e 398Size Epsv
399ListDir Epsv
400Epsv FileOrList
94439e4e 401FileOrList Rest / Retr / Nlst / List / Mkdir (PUT /xxx;type=d)
dbfed404 402Rest Retr
94439e4e 403Retr / Nlst / List DataRead* (on datachannel)
404DataRead* ReadTransferDone
dbfed404 405ReadTransferDone DataTransferDone
94439e4e 406Stor DataWrite* (on datachannel)
407DataWrite* RequestPutBody** (from client)
408RequestPutBody** DataWrite* / WriteTransferDone
409WriteTransferDone DataTransferDone
dbfed404 410DataTransferDone Quit
411Quit -
412************************************************/
3fdadc70 413
63be0a78 414/// \ingroup ServerProtocolFTPInternal
9e008dda
AJ
415FTPSM *FTP_SM_FUNCS[] = {
416 ftpReadWelcome, /* BEGIN */
417 ftpReadUser, /* SENT_USER */
418 ftpReadPass, /* SENT_PASS */
419 ftpReadType, /* SENT_TYPE */
420 ftpReadMdtm, /* SENT_MDTM */
421 ftpReadSize, /* SENT_SIZE */
422 ftpReadEPRT, /* SENT_EPRT */
423 ftpReadPORT, /* SENT_PORT */
424 ftpReadEPSV, /* SENT_EPSV_ALL */
425 ftpReadEPSV, /* SENT_EPSV_1 */
426 ftpReadEPSV, /* SENT_EPSV_2 */
427 ftpReadPasv, /* SENT_PASV */
428 ftpReadCwd, /* SENT_CWD */
429 ftpReadList, /* SENT_LIST */
430 ftpReadList, /* SENT_NLST */
431 ftpReadRest, /* SENT_REST */
432 ftpReadRetr, /* SENT_RETR */
433 ftpReadStor, /* SENT_STOR */
434 ftpReadQuit, /* SENT_QUIT */
435 ftpReadTransferDone, /* READING_DATA (RETR,LIST,NLST) */
436 ftpWriteTransferDone, /* WRITING_DATA (STOR) */
437 ftpReadMkdir /* SENT_MKDIR */
438};
e381a13d 439
6e92b048 440/// handler called by Comm when FTP control channel is closed unexpectedly
9e008dda 441void
94b88585
AR
442FtpStateData::ctrlClosed(const CommCloseCbParams &io)
443{
444 ctrl.clear();
445 deleteThis("FtpStateData::ctrlClosed");
446}
447
6e92b048 448/// handler called by Comm when FTP data channel is closed unexpectedly
9e008dda 449void
94b88585 450FtpStateData::dataClosed(const CommCloseCbParams &io)
518a192e 451{
04f55905
AJ
452 if (data.listener) {
453 delete data.listener;
454 data.listener = NULL;
455 data.fd = -1;
456 }
94b88585 457 data.clear();
5a5b4ce1 458 failed(ERR_FTP_FAILURE, 0);
6e92b048 459 /* failed closes ctrl.fd and frees ftpState */
5a5b4ce1
AJ
460
461 /* NP: failure recovery may be possible when its only a data.fd failure.
462 * is the ctrl.fd is still fine, we can send ABOR down it and retry.
463 * Just need to watch out for wider Squid states like shutting down or reconfigure.
464 */
6f0aab86 465}
1be4874e 466
dc56a9b1 467FtpStateData::FtpStateData(FwdState *theFwdState) : AsyncJob("FtpStateData"), ServerStateData(theFwdState)
6f0aab86 468{
3900307b 469 const char *url = entry->url();
a689bd4e 470 debugs(9, 3, HERE << "'" << url << "'" );
6f0aab86 471 statCounter.server.all.requests++;
472 statCounter.server.ftp.requests++;
47f6e231 473 theSize = -1;
fe6b2914 474 mdtm = -1;
6f0aab86 475
fe6b2914 476 if (Config.Ftp.passive && !theFwdState->ftpPasvFailed())
477 flags.pasv_supported = 1;
6f0aab86 478
fe6b2914 479 flags.rest_supported = 1;
6f0aab86 480
dc56a9b1 481 typedef CommCbMemFunT<FtpStateData, CommCloseCbParams> Dialer;
4299f876 482 AsyncCall::Pointer closer = JobCallback(9, 5,
4cb2536f 483 Dialer, this, FtpStateData::ctrlClosed);
94b88585 484 ctrl.opened(theFwdState->server_fd, closer);
6f0aab86 485
fe6b2914 486 if (request->method == METHOD_PUT)
487 flags.put = 1;
518a192e 488}
489
0353e724 490FtpStateData::~FtpStateData()
ba718c8f 491{
a689bd4e 492 debugs(9, 3, HERE << entry->url() );
62e76326 493
6f0aab86 494 if (reply_hdr) {
495 memFree(reply_hdr, MEM_8K_BUF);
496 reply_hdr = NULL;
497 }
62e76326 498
94b88585 499 data.close();
62e76326 500
6e92b048
AR
501 if (ctrl.fd >= 0) {
502 debugs(9, DBG_IMPORTANT, HERE << "Internal bug: FtpStateData left " <<
9e008dda 503 "control FD " << ctrl.fd << " open");
6e92b048
AR
504 }
505
6f0aab86 506 if (ctrl.buf) {
507 memFreeBuf(ctrl.size, ctrl.buf);
508 ctrl.buf = NULL;
1ecaa0a0 509 }
62e76326 510
968d691c 511 if (data.readBuf) {
512 if (!data.readBuf->isNull())
9e008dda 513 data.readBuf->clean();
253caccb 514
968d691c 515 delete data.readBuf;
516 }
62e76326 517
6f0aab86 518 if (pathcomps)
519 wordlistDestroy(&pathcomps);
62e76326 520
6f0aab86 521 if (ctrl.message)
522 wordlistDestroy(&ctrl.message);
62e76326 523
0477a072 524 cwd_message.clean();
62e76326 525
6f0aab86 526 safe_free(ctrl.last_reply);
62e76326 527
6f0aab86 528 safe_free(ctrl.last_command);
62e76326 529
6f0aab86 530 safe_free(old_request);
62e76326 531
6f0aab86 532 safe_free(old_reply);
62e76326 533
6f0aab86 534 safe_free(old_filepath);
62e76326 535
30abd221 536 title_url.clean();
62e76326 537
30abd221 538 base_href.clean();
62e76326 539
6f0aab86 540 safe_free(filepath);
62e76326 541
6d0440a2 542 safe_free(dirpath);
543
6f0aab86 544 safe_free(data.host);
5f8252d2 545
b6b6f466 546 fwd = NULL; // refcounted
ba718c8f 547}
548
f381d699
AJ
549/**
550 * Parse a possible login username:password pair.
cd0b63ba 551 * Produces filled member variables user, password, password_url if anything found.
f381d699 552 */
6f0aab86 553void
cd0b63ba 554FtpStateData::loginParser(const char *login, int escaped)
090089c4 555{
ccb355fa
AJ
556 const char *u = NULL; // end of the username sub-string
557 int len; // length of the current sub-string to handle.
558
559 int total_len = strlen(login);
d6a8572b 560
f381d699
AJ
561 debugs(9, 4, HERE << ": login='" << login << "', escaped=" << escaped);
562 debugs(9, 9, HERE << ": IN : login='" << login << "', escaped=" << escaped << ", user=" << user << ", password=" << password);
62e76326 563
cd0b63ba 564 if ((u = strchr(login, ':'))) {
62e76326 565
f381d699 566 /* if there was a username part */
cd0b63ba
AJ
567 if (u > login) {
568 len = u - login;
ccb355fa 569 ++u; // jump off the delimiter.
cd0b63ba 570 if (len > MAX_URL)
ccb355fa
AJ
571 len = MAX_URL-1;
572 xstrncpy(user, login, len +1);
573 debugs(9, 9, HERE << ": found user='" << user << "'(" << len <<"), escaped=" << escaped);
f381d699
AJ
574 if (escaped)
575 rfc1738_unescape(user);
ccb355fa 576 debugs(9, 9, HERE << ": found user='" << user << "'(" << len <<") unescaped.");
c4a2d5e1 577 }
62e76326 578
f381d699 579 /* if there was a password part */
ccb355fa
AJ
580 len = login + total_len - u;
581 if ( len > 0) {
582 if (len > MAX_URL)
583 len = MAX_URL -1;
584 xstrncpy(password, u, len +1);
585 debugs(9, 9, HERE << ": found password='" << password << "'(" << len <<"), escaped=" << escaped);
f381d699
AJ
586 if (escaped) {
587 rfc1738_unescape(password);
588 password_url = 1;
589 }
ccb355fa 590 debugs(9, 9, HERE << ": found password='" << password << "'(" << len <<") unescaped.");
f381d699 591 }
04f7fd38 592 } else if (login[0]) {
f381d699 593 /* no password, just username */
ccb355fa
AJ
594 if (total_len > MAX_URL)
595 total_len = MAX_URL -1;
596 xstrncpy(user, login, total_len +1);
597 debugs(9, 9, HERE << ": found user='" << user << "'(" << total_len <<"), escaped=" << escaped);
f381d699
AJ
598 if (escaped)
599 rfc1738_unescape(user);
ccb355fa 600 debugs(9, 9, HERE << ": found user='" << user << "'(" << total_len <<") unescaped.");
f381d699 601 }
62e76326 602
f381d699 603 debugs(9, 9, HERE << ": OUT: login='" << login << "', escaped=" << escaped << ", user=" << user << ", password=" << password);
090089c4 604}
605
6f0aab86 606void
dc56a9b1 607FtpStateData::ftpTimeout(const CommTimeoutCbParams &io)
090089c4 608{
dc56a9b1 609 debugs(9, 4, "ftpTimeout: FD " << io.fd << ": '" << entry->url() << "'" );
62e76326 610
dc56a9b1 611 if (SENT_PASV == state && io.fd == data.fd) {
62e76326 612 /* stupid ftp.netscape.com */
dc56a9b1 613 fwd->dontRetry(false);
614 fwd->ftpPasvFailed(true);
615 debugs(9, DBG_IMPORTANT, "ftpTimeout: timeout in SENT_PASV state" );
7e3ce7b9 616 }
62e76326 617
dc56a9b1 618 failed(ERR_READ_TIMEOUT, 0);
6f0aab86 619 /* failed() closes ctrl.fd and frees ftpState */
090089c4 620}
621
0477a072 622#if DEAD_CODE // obsoleted by ERR_DIR_LISTING
6f0aab86 623void
624FtpStateData::listingFinish()
3fdadc70 625{
0477a072 626 // TODO: figure out what this means and how to show it ...
62e76326 627
6f0aab86 628 if (flags.listformat_unknown && !flags.tried_nlst) {
0477a072 629 printfReplyBody("<a href=\"%s/;type=d\">[As plain directory]</a>\n",
253caccb 630 flags.dir_slash ? rfc1738_escape_part(old_filepath) : ".");
6f0aab86 631 } else if (typecode == 'D') {
632 const char *path = flags.dir_slash ? filepath : ".";
0477a072 633 printfReplyBody("<a href=\"%s/\">[As extended directory]</a>\n", rfc1738_escape_part(path));
dbfed404 634 }
3fdadc70 635}
0477a072 636#endif /* DEAD_CODE */
3fdadc70 637
63be0a78 638/// \ingroup ServerProtocolFTPInternal
9e008dda
AJ
639static const char *Month[] = {
640 "Jan", "Feb", "Mar", "Apr", "May", "Jun",
641 "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"
642};
3fdadc70 643
63be0a78 644/// \ingroup ServerProtocolFTPInternal
3fdadc70 645static int
646is_month(const char *buf)
647{
648 int i;
62e76326 649
3fdadc70 650 for (i = 0; i < 12; i++)
62e76326 651 if (!strcasecmp(buf, Month[i]))
652 return 1;
653
3fdadc70 654 return 0;
655}
656
63be0a78 657/// \ingroup ServerProtocolFTPInternal
3fdadc70 658static void
659ftpListPartsFree(ftpListParts ** parts)
660{
661 safe_free((*parts)->date);
662 safe_free((*parts)->name);
663 safe_free((*parts)->showname);
664 safe_free((*parts)->link);
665 safe_free(*parts);
666}
667
63be0a78 668/// \ingroup ServerProtocolFTPInternal
3fdadc70 669#define MAX_TOKENS 64
670
63be0a78 671/// \ingroup ServerProtocolFTPInternal
3fdadc70 672static ftpListParts *
e55f0142 673ftpListParseParts(const char *buf, struct _ftp_flags flags)
3fdadc70 674{
675 ftpListParts *p = NULL;
676 char *t = NULL;
677 const char *ct = NULL;
678 char *tokens[MAX_TOKENS];
679 int i;
680 int n_tokens;
748f6a15 681 static char tbuf[128];
3fdadc70 682 char *xbuf = NULL;
748f6a15 683 static int scan_ftp_initialized = 0;
684 static regex_t scan_ftp_integer;
685 static regex_t scan_ftp_time;
686 static regex_t scan_ftp_dostime;
687 static regex_t scan_ftp_dosdate;
688
9e008dda 689 if (!scan_ftp_initialized) {
62e76326 690 scan_ftp_initialized = 1;
691 regcomp(&scan_ftp_integer, "^[0123456789]+$", REG_EXTENDED | REG_NOSUB);
692 regcomp(&scan_ftp_time, "^[0123456789:]+$", REG_EXTENDED | REG_NOSUB);
693 regcomp(&scan_ftp_dosdate, "^[0123456789]+-[0123456789]+-[0123456789]+$", REG_EXTENDED | REG_NOSUB);
694 regcomp(&scan_ftp_dostime, "^[0123456789]+:[0123456789]+[AP]M$", REG_EXTENDED | REG_NOSUB | REG_ICASE);
748f6a15 695 }
62e76326 696
3fdadc70 697 if (buf == NULL)
62e76326 698 return NULL;
699
3fdadc70 700 if (*buf == '\0')
62e76326 701 return NULL;
702
e6ccf245 703 p = (ftpListParts *)xcalloc(1, sizeof(ftpListParts));
62e76326 704
3fdadc70 705 n_tokens = 0;
62e76326 706
748f6a15 707 memset(tokens, 0, sizeof(tokens));
62e76326 708
3fdadc70 709 xbuf = xstrdup(buf);
62e76326 710
9e008dda 711 if (flags.tried_nlst) {
62e76326 712 /* Machine readable format, one name per line */
713 p->name = xbuf;
714 p->type = '\0';
715 return p;
dbfed404 716 }
62e76326 717
3fdadc70 718 for (t = strtok(xbuf, w_space); t && n_tokens < MAX_TOKENS; t = strtok(NULL, w_space))
62e76326 719 tokens[n_tokens++] = xstrdup(t);
720
3fdadc70 721 xfree(xbuf);
62e76326 722
3fdadc70 723 /* locate the Month field */
9e008dda 724 for (i = 3; i < n_tokens - 2; i++) {
62e76326 725 char *size = tokens[i - 1];
726 char *month = tokens[i];
727 char *day = tokens[i + 1];
728 char *year = tokens[i + 2];
729
730 if (!is_month(month))
731 continue;
732
733 if (regexec(&scan_ftp_integer, size, 0, NULL, 0) != 0)
734 continue;
735
736 if (regexec(&scan_ftp_integer, day, 0, NULL, 0) != 0)
737 continue;
738
7978e6ca 739 if (regexec(&scan_ftp_time, year, 0, NULL, 0) != 0) /* Yr | hh:mm */
62e76326 740 continue;
741
742 snprintf(tbuf, 128, "%s %2s %5s",
743 month, day, year);
744
745 if (!strstr(buf, tbuf))
746 snprintf(tbuf, 128, "%s %2s %-5s",
747 month, day, year);
748
65bfb9ab 749 char const *copyFrom = NULL;
750
751 if ((copyFrom = strstr(buf, tbuf))) {
62e76326 752 p->type = *tokens[0];
47f6e231 753 p->size = strtoll(size, NULL, 10);
62e76326 754 p->date = xstrdup(tbuf);
755
756 if (flags.skip_whitespace) {
65bfb9ab 757 copyFrom += strlen(tbuf);
62e76326 758
65bfb9ab 759 while (strchr(w_space, *copyFrom))
760 copyFrom++;
62e76326 761 } else {
762 /* XXX assumes a single space between date and filename
763 * suggested by: Nathan.Bailey@cc.monash.edu.au and
764 * Mike Battersby <mike@starbug.bofh.asn.au> */
65bfb9ab 765 copyFrom += strlen(tbuf) + 1;
62e76326 766 }
767
65bfb9ab 768 p->name = xstrdup(copyFrom);
62e76326 769
38ebaefc 770 if (p->type == 'l' && (t = strstr(p->name, " -> "))) {
62e76326 771 *t = '\0';
772 p->link = xstrdup(t + 4);
773 }
774
775 goto found;
776 }
777
778 break;
3fdadc70 779 }
62e76326 780
748f6a15 781 /* try it as a DOS listing, 04-05-70 09:33PM ... */
782 if (n_tokens > 3 &&
62e76326 783 regexec(&scan_ftp_dosdate, tokens[0], 0, NULL, 0) == 0 &&
9e008dda 784 regexec(&scan_ftp_dostime, tokens[1], 0, NULL, 0) == 0) {
62e76326 785 if (!strcasecmp(tokens[2], "<dir>")) {
786 p->type = 'd';
787 } else {
788 p->type = '-';
47f6e231 789 p->size = strtoll(tokens[2], NULL, 10);
62e76326 790 }
791
792 snprintf(tbuf, 128, "%s %s", tokens[0], tokens[1]);
793 p->date = xstrdup(tbuf);
794
795 if (p->type == 'd') {
796 /* Directory.. name begins with first printable after <dir> */
797 ct = strstr(buf, tokens[2]);
798 ct += strlen(tokens[2]);
799
800 while (xisspace(*ct))
801 ct++;
802
803 if (!*ct)
804 ct = NULL;
805 } else {
806 /* A file. Name begins after size, with a space in between */
807 snprintf(tbuf, 128, " %s %s", tokens[2], tokens[3]);
808 ct = strstr(buf, tbuf);
809
810 if (ct) {
811 ct += strlen(tokens[2]) + 2;
812 }
813 }
814
815 p->name = xstrdup(ct ? ct : tokens[3]);
816 goto found;
3fdadc70 817 }
62e76326 818
3fdadc70 819 /* Try EPLF format; carson@lehman.com */
9e008dda 820 if (buf[0] == '+') {
62e76326 821 ct = buf + 1;
822 p->type = 0;
823
824 while (ct && *ct) {
d5f8d05f 825 time_t tm;
3f9d0b2d 826 int l = strcspn(ct, ",");
62e76326 827 char *tmp;
828
829 if (l < 1)
830 goto blank;
831
832 switch (*ct) {
833
834 case '\t':
835 p->name = xstrndup(ct + 1, l + 1);
836 break;
837
838 case 's':
839 p->size = atoi(ct + 1);
840 break;
841
842 case 'm':
d5f8d05f 843 tm = (time_t) strtol(ct + 1, &tmp, 0);
62e76326 844
3f9d0b2d 845 if (tmp != ct + 1)
62e76326 846 break; /* not a valid integer */
847
d5f8d05f 848 p->date = xstrdup(ctime(&tm));
62e76326 849
850 *(strstr(p->date, "\n")) = '\0';
851
852 break;
853
854 case '/':
855 p->type = 'd';
856
857 break;
858
859 case 'r':
860 p->type = '-';
861
862 break;
863
864 case 'i':
865 break;
866
867 default:
868 break;
869 }
870
871blank:
872 ct = strstr(ct, ",");
873
874 if (ct) {
875 ct++;
876 }
877 }
878
879 if (p->type == 0) {
880 p->type = '-';
881 }
882
883 if (p->name)
884 goto found;
885 else
886 safe_free(p->date);
887 }
888
889found:
890
3fdadc70 891 for (i = 0; i < n_tokens; i++)
62e76326 892 xfree(tokens[i]);
893
748f6a15 894 if (!p->name)
62e76326 895 ftpListPartsFree(&p); /* cleanup */
896
3fdadc70 897 return p;
898}
899
0477a072 900MemBuf *
6f0aab86 901FtpStateData::htmlifyListEntry(const char *line)
3fdadc70 902{
0477a072
AJ
903 char icon[2048];
904 char href[2048 + 40];
905 char text[ 2048];
906 char size[ 2048];
907 char chdir[ 2048 + 40];
908 char view[ 2048 + 40];
909 char download[ 2048 + 40];
910 char link[ 2048 + 40];
911 MemBuf *html;
912 char prefix[2048];
3fdadc70 913 ftpListParts *parts;
0477a072 914 *icon = *href = *text = *size = *chdir = *view = *download = *link = '\0';
62e76326 915
0477a072 916 debugs(9, 7, HERE << " line ={" << line << "}");
62e76326 917
0477a072
AJ
918 if (strlen(line) > 1024) {
919 html = new MemBuf();
920 html->init();
921 html->Printf("<tr><td colspan=\"5\">%s</td></tr>\n", line);
62e76326 922 return html;
3fdadc70 923 }
62e76326 924
45289935 925 if (flags.dir_slash && dirpath && typecode != 'D')
926 snprintf(prefix, 2048, "%s/", rfc1738_escape_part(dirpath));
5627a633 927 else
928 prefix[0] = '\0';
929
6f0aab86 930 if ((parts = ftpListParseParts(line, flags)) == NULL) {
62e76326 931 const char *p;
0477a072
AJ
932
933 html = new MemBuf();
934 html->init();
935 html->Printf("<tr class=\"entry\"><td colspan=\"5\">%s</td></tr>\n", line);
62e76326 936
3d0ac046 937 for (p = line; *p && xisspace(*p); p++);
62e76326 938 if (*p && !xisspace(*p))
6f0aab86 939 flags.listformat_unknown = 1;
62e76326 940
941 return html;
13e80e5b 942 }
62e76326 943
3fdadc70 944 if (!strcmp(parts->name, ".") || !strcmp(parts->name, "..")) {
62e76326 945 ftpListPartsFree(&parts);
0477a072 946 return NULL;
3fdadc70 947 }
62e76326 948
3fdadc70 949 parts->size += 1023;
950 parts->size >>= 10;
951 parts->showname = xstrdup(parts->name);
62e76326 952
2a1ca944 953 /* {icon} {text} . . . {date}{size}{chdir}{view}{download}{link}\n */
9bc73deb 954 xstrncpy(href, rfc1738_escape_part(parts->name), 2048);
62e76326 955
2a1ca944 956 xstrncpy(text, parts->showname, 2048);
62e76326 957
3fdadc70 958 switch (parts->type) {
62e76326 959
3fdadc70 960 case 'd':
0477a072 961 snprintf(icon, 2048, "<img border=\"0\" src=\"%s\" alt=\"%-6s\">",
62e76326 962 mimeGetIconURL("internal-dir"),
963 "[DIR]");
964 strcat(href, "/"); /* margin is allocated above */
965 break;
966
3fdadc70 967 case 'l':
0477a072 968 snprintf(icon, 2048, "<img border=\"0\" src=\"%s\" alt=\"%-6s\">",
62e76326 969 mimeGetIconURL("internal-link"),
970 "[LINK]");
971 /* sometimes there is an 'l' flag, but no "->" link */
972
973 if (parts->link) {
974 char *link2 = xstrdup(html_quote(rfc1738_escape(parts->link)));
0477a072 975 snprintf(link, 2048, " -&gt; <a href=\"%s%s\">%s</a>",
5627a633 976 *link2 != '/' ? prefix : "", link2,
62e76326 977 html_quote(parts->link));
978 safe_free(link2);
979 }
980
981 break;
982
dbfed404 983 case '\0':
0477a072 984 snprintf(icon, 2048, "<img border=\"0\" src=\"%s\" alt=\"%-6s\">",
62e76326 985 mimeGetIconURL(parts->name),
986 "[UNKNOWN]");
0477a072
AJ
987 snprintf(chdir, 2048, "<a href=\"%s/;type=d\"><img border=\"0\" src=\"%s\" "
988 "alt=\"[DIR]\"></a>",
62e76326 989 rfc1738_escape_part(parts->name),
990 mimeGetIconURL("internal-dir"));
991 break;
992
3fdadc70 993 case '-':
62e76326 994
3fdadc70 995 default:
0477a072 996 snprintf(icon, 2048, "<img border=\"0\" src=\"%s\" alt=\"%-6s\">",
62e76326 997 mimeGetIconURL(parts->name),
998 "[FILE]");
47f6e231 999 snprintf(size, 2048, " %6"PRId64"k", parts->size);
62e76326 1000 break;
3fdadc70 1001 }
62e76326 1002
2a1ca944 1003 if (parts->type != 'd') {
62e76326 1004 if (mimeGetViewOption(parts->name)) {
0477a072
AJ
1005 snprintf(view, 2048, "<a href=\"%s%s;type=a\"><img border=\"0\" src=\"%s\" "
1006 "alt=\"[VIEW]\"></a>",
5627a633 1007 prefix, href, mimeGetIconURL("internal-view"));
62e76326 1008 }
1009
1010 if (mimeGetDownloadOption(parts->name)) {
0477a072
AJ
1011 snprintf(download, 2048, "<a href=\"%s%s;type=i\"><img border=\"0\" src=\"%s\" "
1012 "alt=\"[DOWNLOAD]\"></a>",
5627a633 1013 prefix, href, mimeGetIconURL("internal-download"));
62e76326 1014 }
2a1ca944 1015 }
62e76326 1016
0477a072
AJ
1017 /* construct the table row from parts. */
1018 html = new MemBuf();
1019 html->init();
1020 html->Printf("<tr class=\"entry\">"
05320519
A
1021 "<td class=\"icon\"><a href=\"%s%s\">%s</a></td>"
1022 "<td class=\"filename\"><a href=\"%s%s\">%s</a></td>"
1023 "<td class=\"date\">%s</td>"
1024 "<td class=\"size\">%s</td>"
1025 "<td class=\"actions\">%s%s%s%s</td>"
1026 "</tr>\n",
1027 prefix, href, icon,
1028 prefix, href, html_quote(text),
1029 parts->date,
1030 size,
62e76326 1031 chdir, view, download, link);
62e76326 1032
3fdadc70 1033 ftpListPartsFree(&parts);
3fdadc70 1034 return html;
1035}
1036
f1a83a7d 1037void
1038FtpStateData::parseListing()
3fdadc70 1039{
253caccb 1040 char *buf = data.readBuf->content();
a313a9ba 1041 char *sbuf; /* NULL-terminated copy of termedBuf */
b5639035 1042 char *end;
1043 char *line;
3fdadc70 1044 char *s;
0477a072 1045 MemBuf *t;
3fdadc70 1046 size_t linelen;
b5639035 1047 size_t usable;
253caccb 1048 size_t len = data.readBuf->contentSize();
5f8252d2 1049
1050 if (!len) {
0477a072 1051 debugs(9, 3, HERE << "no content to parse for " << entry->url() );
5f8252d2 1052 return;
1053 }
1054
7131112f 1055 /*
1056 * We need a NULL-terminated buffer for scanning, ick
1057 */
e6ccf245 1058 sbuf = (char *)xmalloc(len + 1);
7131112f 1059 xstrncpy(sbuf, buf, len + 1);
1060 end = sbuf + len - 1;
62e76326 1061
7131112f 1062 while (*end != '\r' && *end != '\n' && end > sbuf)
62e76326 1063 end--;
1064
7131112f 1065 usable = end - sbuf;
62e76326 1066
a689bd4e 1067 debugs(9, 3, HERE << "usable = " << usable);
62e76326 1068
b5639035 1069 if (usable == 0) {
0477a072 1070 debugs(9, 3, HERE << "didn't find end for " << entry->url() );
62e76326 1071 xfree(sbuf);
1072 return;
3fdadc70 1073 }
62e76326 1074
a689bd4e 1075 debugs(9, 3, HERE << (unsigned long int)len << " bytes to play with");
bf8fe701 1076
e6ccf245 1077 line = (char *)memAllocate(MEM_4K_BUF);
3fdadc70 1078 end++;
362be274 1079 s = sbuf;
1080 s += strspn(s, crlf);
62e76326 1081
362be274 1082 for (; s < end; s += strcspn(s, crlf), s += strspn(s, crlf)) {
a689bd4e 1083 debugs(9, 7, HERE << "s = {" << s << "}");
62e76326 1084 linelen = strcspn(s, crlf) + 1;
1085
1086 if (linelen < 2)
1087 break;
1088
1089 if (linelen > 4096)
1090 linelen = 4096;
1091
1092 xstrncpy(line, s, linelen);
1093
a689bd4e 1094 debugs(9, 7, HERE << "{" << line << "}");
62e76326 1095
1096 if (!strncmp(line, "total", 5))
1097 continue;
1098
f1a83a7d 1099 t = htmlifyListEntry(line);
62e76326 1100
05320519 1101 if ( t != NULL) {
0477a072
AJ
1102 debugs(9, 7, HERE << "listing append: t = {" << t->contentSize() << ", '" << t->content() << "'}");
1103 listing.append(t->content(), t->contentSize());
1104//leak? delete t;
1105 }
b5639035 1106 }
62e76326 1107
0477a072 1108 debugs(9, 7, HERE << "Done.");
253caccb 1109 data.readBuf->consume(usable);
db1cd23c 1110 memFree(line, MEM_4K_BUF);
4a2635aa 1111 xfree(sbuf);
3fdadc70 1112}
090089c4 1113
5f8252d2 1114int
9e008dda
AJ
1115FtpStateData::dataDescriptor() const
1116{
5f8252d2 1117 return data.fd;
1118}
1119
f1a83a7d 1120void
1121FtpStateData::dataComplete()
79a15e0a 1122{
a689bd4e 1123 debugs(9, 3,HERE);
62e76326 1124
a689bd4e 1125 /* Connection closed; transfer done. */
94b88585
AR
1126
1127 /// Close data channel, if any, to conserve resources while we wait.
1128 data.close();
62e76326 1129
79a15e0a 1130 /* expect the "transfer complete" message on the control socket */
4e849646 1131 /*
1132 * DPW 2007-04-23
1133 * Previously, this was the only place where we set the
1134 * 'buffered_ok' flag when calling scheduleReadControlReply().
1135 * It caused some problems if the FTP server returns an unexpected
1136 * status code after the data command. FtpStateData was being
1137 * deleted in the middle of dataRead().
1138 */
1139 scheduleReadControlReply(0);
79a15e0a 1140}
1141
253caccb 1142void
5f8252d2 1143FtpStateData::maybeReadVirginBody()
253caccb 1144{
1145 if (data.fd < 0)
1146 return;
1147
1148 if (data.read_pending)
1149 return;
1150
52edecde 1151 const int read_sz = replyBodySpace(*data.readBuf, 0);
253caccb 1152
1153 debugs(11,9, HERE << "FTP may read up to " << read_sz << " bytes");
1154
1155 if (read_sz < 2) // see http.cc
1156 return;
1157
1158 data.read_pending = true;
1159
dc56a9b1 1160 typedef CommCbMemFunT<FtpStateData, CommTimeoutCbParams> TimeoutDialer;
4299f876 1161 AsyncCall::Pointer timeoutCall = JobCallback(9, 5,
4cb2536f 1162 TimeoutDialer, this, FtpStateData::ftpTimeout);
dc56a9b1 1163 commSetTimeout(data.fd, Config.Timeout.read, timeoutCall);
928720a6 1164
253caccb 1165 debugs(9,5,HERE << "queueing read on FD " << data.fd);
1166
dc56a9b1 1167 typedef CommCbMemFunT<FtpStateData, CommIoCbParams> Dialer;
1168 entry->delayAwareRead(data.fd, data.readBuf->space(), read_sz,
4cb2536f 1169 JobCallback(9, 5, Dialer, this, FtpStateData::dataRead));
253caccb 1170}
1171
f1a83a7d 1172void
dc56a9b1 1173FtpStateData::dataRead(const CommIoCbParams &io)
f1a83a7d 1174{
a57512fa 1175 int j;
30a4f2a8 1176 int bin;
62e76326 1177
dc56a9b1 1178 data.read_pending = false;
1179
1180 debugs(9, 3, HERE << "ftpDataRead: FD " << io.fd << " Read " << io.size << " bytes");
62e76326 1181
dc56a9b1 1182 if (io.size > 0) {
1183 kb_incr(&statCounter.server.all.kbytes_in, io.size);
1184 kb_incr(&statCounter.server.ftp.kbytes_in, io.size);
3c7568c7 1185 }
62e76326 1186
dc56a9b1 1187 if (io.flag == COMM_ERR_CLOSING)
62e76326 1188 return;
3c7568c7 1189
dc56a9b1 1190 assert(io.fd == data.fd);
3c7568c7 1191
e92e4e44 1192 if (EBIT_TEST(entry->flags, ENTRY_ABORTED)) {
5f8252d2 1193 abortTransaction("entry aborted during dataRead");
62e76326 1194 return;
e92e4e44 1195 }
c4b7a5a9 1196
dc56a9b1 1197 if (io.flag == COMM_OK && io.size > 0) {
1198 debugs(9,5,HERE << "appended " << io.size << " bytes to readBuf");
1199 data.readBuf->appended(io.size);
253caccb 1200#if DELAY_POOLS
253caccb 1201 DelayId delayId = entry->mem_obj->mostBytesAllowed();
dc56a9b1 1202 delayId.bytesIn(io.size);
253caccb 1203#endif
62e76326 1204 IOStats.Ftp.reads++;
1205
dc56a9b1 1206 for (j = io.size - 1, bin = 0; j; bin++)
62e76326 1207 j >>= 1;
1208
1209 IOStats.Ftp.read_hist[bin]++;
30a4f2a8 1210 }
62e76326 1211
dc56a9b1 1212 if (io.flag != COMM_OK || io.size < 0) {
9e008dda
AJ
1213 debugs(50, ignoreErrno(io.xerrno) ? 3 : DBG_IMPORTANT,
1214 "ftpDataRead: read error: " << xstrerr(io.xerrno));
dc56a9b1 1215
1216 if (ignoreErrno(io.xerrno)) {
1217 typedef CommCbMemFunT<FtpStateData, CommTimeoutCbParams> TimeoutDialer;
4299f876 1218 AsyncCall::Pointer timeoutCall = JobCallback(9, 5,
4cb2536f 1219 TimeoutDialer, this, FtpStateData::ftpTimeout);
dc56a9b1 1220 commSetTimeout(io.fd, Config.Timeout.read, timeoutCall);
62e76326 1221
5f8252d2 1222 maybeReadVirginBody();
62e76326 1223 } else {
0477a072 1224 if (!flags.http_header_sent && !fwd->ftpPasvFailed() && flags.pasv_supported && !flags.listing) {
f1a83a7d 1225 fwd->dontRetry(false); /* this is a retryable error */
1226 fwd->ftpPasvFailed(true);
18ed7c61 1227 }
1228
f1a83a7d 1229 failed(ERR_READ_ERROR, 0);
6f0aab86 1230 /* failed closes ctrl.fd and frees ftpState */
62e76326 1231 return;
1232 }
dc56a9b1 1233 } else if (io.size == 0) {
1234 debugs(9,3, HERE << "Calling dataComplete() because io.size == 0");
9e008dda
AJ
1235 /*
1236 * DPW 2007-04-23
1237 * Dangerous curves ahead. This call to dataComplete was
1238 * calling scheduleReadControlReply, handleControlReply,
1239 * and then ftpReadTransferDone. If ftpReadTransferDone
1240 * gets unexpected status code, it closes down the control
1241 * socket and our FtpStateData object gets destroyed. As
1242 * a workaround we no longer set the 'buffered_ok' flag in
1243 * the scheduleReadControlReply call.
1244 */
f1a83a7d 1245 dataComplete();
253caccb 1246 }
62e76326 1247
253caccb 1248 processReplyBody();
1249}
b66315e4 1250
253caccb 1251void
1252FtpStateData::processReplyBody()
1253{
dc56a9b1 1254 debugs(9, 3, HERE << "FtpStateData::processReplyBody starting.");
063a47b5 1255
47f6e231 1256 if (request->method == METHOD_HEAD && (flags.isdir || theSize != -1)) {
063a47b5 1257 serverComplete();
1258 return;
1259 }
1260
0477a072
AJ
1261 /* Directory listings are special. They write ther own headers via the error objects */
1262 if (!flags.http_header_sent && data.readBuf->contentSize() >= 0 && !flags.isdir)
253caccb 1263 appendSuccessHeader();
62e76326 1264
35f26998 1265 if (EBIT_TEST(entry->flags, ENTRY_ABORTED)) {
9e008dda
AJ
1266 /*
1267 * probably was aborted because content length exceeds one
1268 * of the maximum size limits.
1269 */
35f26998 1270 abortTransaction("entry aborted after calling appendSuccessHeader()");
1271 return;
1272 }
1273
a83c6ed6 1274#if USE_ADAPTATION
62e76326 1275
a83c6ed6
AR
1276 if (adaptationAccessCheckPending) {
1277 debugs(9,3, HERE << "returning from FtpStateData::processReplyBody due to adaptationAccessCheckPending");
253caccb 1278 return;
1279 }
62e76326 1280
c4b7a5a9 1281#endif
62e76326 1282
253caccb 1283 if (flags.isdir) {
0477a072
AJ
1284 if (!flags.listing) {
1285 flags.listing = 1;
1286 listing.reset();
1287 }
253caccb 1288 parseListing();
0477a072
AJ
1289 maybeReadVirginBody();
1290 return;
e1381638
AJ
1291 } else if (const int csize = data.readBuf->contentSize()) {
1292 writeReplyBody(data.readBuf->content(), csize);
1293 debugs(9, 5, HERE << "consuming " << csize << " bytes of readBuf");
1294 data.readBuf->consume(csize);
1295 }
253caccb 1296
3900307b 1297 entry->flush();
253caccb 1298
5f8252d2 1299 maybeReadVirginBody();
090089c4 1300}
1301
63be0a78 1302/**
f381d699
AJ
1303 * Locates the FTP user:password login.
1304 *
1305 * Highest to lowest priority:
1306 * - Checks URL (ftp://user:pass@domain)
1307 * - Authorization: Basic header
1308 * - squid.conf anonymous-FTP settings (default: anonymous:Squid@).
1309 *
1310 * Special Case: A username-only may be provided in the URL and password in the HTTP headers.
1311 *
4e2da309
AJ
1312 * TODO: we might be able to do something about locating username from other sources:
1313 * ie, external ACL user=* tag or ident lookup
1314 *
63be0a78 1315 \retval 1 if we have everything needed to complete this request.
1316 \retval 0 if something is missing.
429fdbec 1317 */
f1a83a7d 1318int
1319FtpStateData::checkAuth(const HttpHeader * req_hdr)
429fdbec 1320{
63259c34 1321 const char *auth;
62e76326 1322
f381d699
AJ
1323 /* default username */
1324 xstrncpy(user, "anonymous", MAX_URL);
62e76326 1325
f381d699
AJ
1326 /* Check HTTP Authorization: headers (better than defaults, but less than URL) */
1327 if ( (auth = req_hdr->getAuth(HDR_AUTHORIZATION, "Basic")) ) {
1328 flags.authenticated = 1;
1329 loginParser(auth, FTP_LOGIN_NOT_ESCAPED);
1330 }
1331 /* we fail with authorization-required error later IFF the FTP server requests it */
62e76326 1332
f381d699
AJ
1333 /* Test URL login syntax. Overrides any headers received. */
1334 loginParser(request->login, FTP_LOGIN_ESCAPED);
62e76326 1335
f381d699
AJ
1336 /* name is missing. thats fatal. */
1337 if (!user[0])
1338 fatal("FTP login parsing destroyed username info");
62e76326 1339
f381d699
AJ
1340 /* name + password == success */
1341 if (password[0])
1342 return 1;
62e76326 1343
f381d699
AJ
1344 /* Setup default FTP password settings */
1345 /* this has to be done last so that we can have a no-password case above. */
1346 if (!password[0]) {
4e2da309 1347 if (strcmp(user, "anonymous") == 0 && !flags.tried_auth_anonymous) {
f381d699 1348 xstrncpy(password, Config.Ftp.anon_user, MAX_URL);
4e2da309
AJ
1349 flags.tried_auth_anonymous=1;
1350 return 1;
e1381638 1351 } else if (!flags.tried_auth_nopass) {
f381d699 1352 xstrncpy(password, null_string, MAX_URL);
4e2da309
AJ
1353 flags.tried_auth_nopass=1;
1354 return 1;
1355 }
429fdbec 1356 }
62e76326 1357
429fdbec 1358 return 0; /* different username */
1359}
1360
811d914c 1361static String str_type_eq;
f1a83a7d 1362void
1363FtpStateData::checkUrlpath()
13e80e5b 1364{
13e80e5b 1365 int l;
9b558d8a 1366 size_t t;
62e76326 1367
811d914c
FC
1368 if (str_type_eq.undefined()) //hack. String doesn't support global-static
1369 str_type_eq="type=";
1370
2c1fd837 1371 if ((t = request->urlpath.rfind(';')) != String::npos) {
9b558d8a
FC
1372 if (request->urlpath.substr(t+1,t+1+str_type_eq.size())==str_type_eq) {
1373 typecode = (char)xtoupper(request->urlpath[t+str_type_eq.size()+1]);
1374 request->urlpath.cut(t);
62e76326 1375 }
dbfed404 1376 }
62e76326 1377
528b2c61 1378 l = request->urlpath.size();
13e80e5b 1379 /* check for null path */
62e76326 1380
02922e76 1381 if (!l) {
f1a83a7d 1382 flags.isdir = 1;
1383 flags.root_dir = 1;
1384 flags.need_base_href = 1; /* Work around broken browsers */
30abd221 1385 } else if (!request->urlpath.cmp("/%2f/")) {
62e76326 1386 /* UNIX root directory */
f1a83a7d 1387 flags.isdir = 1;
1388 flags.root_dir = 1;
a313a9ba 1389 } else if ((l >= 1) && (request->urlpath[l - 1] == '/')) {
62e76326 1390 /* Directory URL, ending in / */
f1a83a7d 1391 flags.isdir = 1;
62e76326 1392
1393 if (l == 1)
f1a83a7d 1394 flags.root_dir = 1;
d5f80edc 1395 } else {
f1a83a7d 1396 flags.dir_slash = 1;
13e80e5b 1397 }
1398}
1399
f1a83a7d 1400void
1401FtpStateData::buildTitleUrl()
3fdadc70 1402{
f1a83a7d 1403 title_url = "ftp://";
62e76326 1404
f1a83a7d 1405 if (strcmp(user, "anonymous")) {
1406 title_url.append(user);
1407 title_url.append("@");
06a5d871 1408 }
62e76326 1409
cc192b50 1410 title_url.append(request->GetHost());
62e76326 1411
06a5d871 1412 if (request->port != urlDefaultPort(PROTO_FTP)) {
f1a83a7d 1413 title_url.append(":");
1414 title_url.append(xitoa(request->port));
3fdadc70 1415 }
62e76326 1416
f1a83a7d 1417 title_url.append (request->urlpath);
06a5d871 1418
f1a83a7d 1419 base_href = "ftp://";
62e76326 1420
f1a83a7d 1421 if (strcmp(user, "anonymous") != 0) {
1422 base_href.append(rfc1738_escape_part(user));
62e76326 1423
f1a83a7d 1424 if (password_url) {
1425 base_href.append (":");
1426 base_href.append(rfc1738_escape_part(password));
62e76326 1427 }
1428
f1a83a7d 1429 base_href.append("@");
06a5d871 1430 }
62e76326 1431
cc192b50 1432 base_href.append(request->GetHost());
62e76326 1433
06a5d871 1434 if (request->port != urlDefaultPort(PROTO_FTP)) {
f1a83a7d 1435 base_href.append(":");
1436 base_href.append(xitoa(request->port));
9bc73deb 1437 }
62e76326 1438
f1a83a7d 1439 base_href.append(request->urlpath);
1440 base_href.append("/");
3fdadc70 1441}
1442
63be0a78 1443/// \ingroup ServerProtocolFTPAPI
770f051d 1444void
db1cd23c 1445ftpStart(FwdState * fwd)
0a0bf5db 1446{
6f0aab86 1447 FtpStateData *ftpState = new FtpStateData(fwd);
1448 ftpState->start();
1449}
62e76326 1450
6f0aab86 1451void
1452FtpStateData::start()
1453{
f1a83a7d 1454 if (!checkAuth(&request->header)) {
62e76326 1455 /* create appropriate reply */
f381d699 1456 HttpReply *reply = ftpAuthRequired(request, ftpRealm());
db237875 1457 entry->replaceHttpReply(reply);
5f8252d2 1458 serverComplete();
62e76326 1459 return;
e381a13d 1460 }
62e76326 1461
f1a83a7d 1462 checkUrlpath();
1463 buildTitleUrl();
a689bd4e 1464 debugs(9, 5, HERE << "host=" << request->GetHost() << ", path=" <<
d53b3f6d 1465 request->urlpath << ", user=" << user << ", passwd=" <<
bf8fe701 1466 password);
1467
6f0aab86 1468 state = BEGIN;
1469 ctrl.last_command = xstrdup("Connect to server");
1470 ctrl.buf = (char *)memAllocBuf(4096, &ctrl.size);
1471 ctrl.offset = 0;
253caccb 1472 data.readBuf = new MemBuf;
1473 data.readBuf->init(4096, SQUID_TCP_SO_RCVBUF);
6f0aab86 1474 scheduleReadControlReply(0);
090089c4 1475}
1476
3fdadc70 1477/* ====================================================================== */
1478
63be0a78 1479/// \ingroup ServerProtocolFTPInternal
dad0fe12 1480static char *
1481escapeIAC(const char *buf)
1482{
1483 int n;
1484 char *ret;
1485 unsigned const char *p;
1486 unsigned char *r;
1487
1488 for (p = (unsigned const char *)buf, n = 1; *p; n++, p++)
1489 if (*p == 255)
1490 n++;
1491
1492 ret = (char *)xmalloc(n);
1493
1494 for (p = (unsigned const char *)buf, r=(unsigned char *)ret; *p; p++) {
1495 *r++ = *p;
1496
1497 if (*p == 255)
1498 *r++ = 255;
1499 }
1500
1501 *r++ = '\0';
1502 assert((r - (unsigned char *)ret) == n );
1503 return ret;
1504}
1505
2f47fadf 1506void
1507FtpStateData::writeCommand(const char *buf)
234967c9 1508{
dad0fe12 1509 char *ebuf;
a689bd4e 1510 /* trace FTP protocol communications at level 2 */
1511 debugs(9, 2, "ftp<< " << buf);
dad0fe12 1512
1513 if (Config.Ftp.telnet)
1514 ebuf = escapeIAC(buf);
1515 else
1516 ebuf = xstrdup(buf);
1517
2f47fadf 1518 safe_free(ctrl.last_command);
dad0fe12 1519
2f47fadf 1520 safe_free(ctrl.last_reply);
dad0fe12 1521
2f47fadf 1522 ctrl.last_command = ebuf;
dad0fe12 1523
a0297974
AR
1524 if (!canSend(ctrl.fd)) {
1525 debugs(9, 2, HERE << "cannot send to closing ctrl FD " << ctrl.fd);
1526 // TODO: assert(ctrl.closer != NULL);
1527 return;
1528 }
1529
dc56a9b1 1530 typedef CommCbMemFunT<FtpStateData, CommIoCbParams> Dialer;
4299f876 1531 AsyncCall::Pointer call = JobCallback(9, 5,
4cb2536f 1532 Dialer, this, FtpStateData::ftpWriteCommandCallback);
2f47fadf 1533 comm_write(ctrl.fd,
1534 ctrl.last_command,
1535 strlen(ctrl.last_command),
9e008dda 1536 call);
dad0fe12 1537
2f47fadf 1538 scheduleReadControlReply(0);
3fdadc70 1539}
1540
6f0aab86 1541void
dc56a9b1 1542FtpStateData::ftpWriteCommandCallback(const CommIoCbParams &io)
3fdadc70 1543{
6800b5b5 1544
dc56a9b1 1545 debugs(9, 5, "ftpWriteCommandCallback: wrote " << io.size << " bytes");
62e76326 1546
dc56a9b1 1547 if (io.size > 0) {
1548 fd_bytes(io.fd, io.size, FD_WRITE);
1549 kb_incr(&statCounter.server.all.kbytes_out, io.size);
1550 kb_incr(&statCounter.server.ftp.kbytes_out, io.size);
ee1679df 1551 }
62e76326 1552
dc56a9b1 1553 if (io.flag == COMM_ERR_CLOSING)
62e76326 1554 return;
1555
dc56a9b1 1556 if (io.flag) {
1557 debugs(9, DBG_IMPORTANT, "ftpWriteCommandCallback: FD " << io.fd << ": " << xstrerr(io.xerrno));
1558 failed(ERR_WRITE_ERROR, io.xerrno);
6f0aab86 1559 /* failed closes ctrl.fd and frees ftpState */
62e76326 1560 return;
3fdadc70 1561 }
1562}
1563
6f0aab86 1564wordlist *
47f6e231 1565FtpStateData::ftpParseControlReply(char *buf, size_t len, int *codep, size_t *used)
3fdadc70 1566{
1567 char *s;
4f310655 1568 char *sbuf;
1569 char *end;
1570 int usable;
3fdadc70 1571 int complete = 0;
51eeadc6 1572 wordlist *head = NULL;
3fdadc70 1573 wordlist *list;
1574 wordlist **tail = &head;
47f6e231 1575 size_t offset;
3fdadc70 1576 size_t linelen;
b5639035 1577 int code = -1;
a689bd4e 1578 debugs(9, 3, HERE);
4f310655 1579 /*
1580 * We need a NULL-terminated buffer for scanning, ick
1581 */
e6ccf245 1582 sbuf = (char *)xmalloc(len + 1);
4f310655 1583 xstrncpy(sbuf, buf, len + 1);
1584 end = sbuf + len - 1;
62e76326 1585
4f310655 1586 while (*end != '\r' && *end != '\n' && end > sbuf)
62e76326 1587 end--;
1588
4f310655 1589 usable = end - sbuf;
62e76326 1590
a689bd4e 1591 debugs(9, 3, HERE << "usable = " << usable);
62e76326 1592
4f310655 1593 if (usable == 0) {
a689bd4e 1594 debugs(9, 3, HERE << "didn't find end of line");
62e76326 1595 safe_free(sbuf);
1596 return NULL;
4f310655 1597 }
62e76326 1598
a689bd4e 1599 debugs(9, 3, HERE << len << " bytes to play with");
4f310655 1600 end++;
1601 s = sbuf;
1602 s += strspn(s, crlf);
62e76326 1603
4f310655 1604 for (; s < end; s += strcspn(s, crlf), s += strspn(s, crlf)) {
62e76326 1605 if (complete)
1606 break;
1607
a689bd4e 1608 debugs(9, 5, HERE << "s = {" << s << "}");
62e76326 1609
1610 linelen = strcspn(s, crlf) + 1;
1611
1612 if (linelen < 2)
1613 break;
1614
1615 if (linelen > 3)
1616 complete = (*s >= '0' && *s <= '9' && *(s + 3) == ' ');
1617
1618 if (complete)
1619 code = atoi(s);
1620
1621 offset = 0;
1622
1623 if (linelen > 3)
1624 if (*s >= '0' && *s <= '9' && (*(s + 3) == '-' || *(s + 3) == ' '))
1625 offset = 4;
1626
d295d770 1627 list = new wordlist();
62e76326 1628
1629 list->key = (char *)xmalloc(linelen - offset);
1630
1631 xstrncpy(list->key, s + offset, linelen - offset);
1632
a689bd4e 1633 /* trace the FTP communication chat at level 2 */
1634 debugs(9, 2, "ftp>> " << code << " " << list->key);
62e76326 1635
1636 *tail = list;
1637
1638 tail = &list->next;
3fdadc70 1639 }
62e76326 1640
47f6e231 1641 *used = (size_t) (s - sbuf);
1c34d125 1642 safe_free(sbuf);
62e76326 1643
3fdadc70 1644 if (!complete)
62e76326 1645 wordlistDestroy(&head);
1646
3fdadc70 1647 if (codep)
62e76326 1648 *codep = code;
1649
3fdadc70 1650 return head;
1651}
1652
63be0a78 1653/**
4e849646 1654 * DPW 2007-04-23
1655 * Looks like there are no longer anymore callers that set
1656 * buffered_ok=1. Perhaps it can be removed at some point.
1657 */
6f0aab86 1658void
1659FtpStateData::scheduleReadControlReply(int buffered_ok)
4f310655 1660{
a689bd4e 1661 debugs(9, 3, HERE << "FD " << ctrl.fd);
62e76326 1662
6f0aab86 1663 if (buffered_ok && ctrl.offset > 0) {
62e76326 1664 /* We've already read some reply data */
6f0aab86 1665 handleControlReply();
4f310655 1666 } else {
62e76326 1667 /* XXX What about Config.Timeout.read? */
9e008dda 1668 typedef CommCbMemFunT<FtpStateData, CommIoCbParams> Dialer;
4299f876 1669 AsyncCall::Pointer reader = JobCallback(9, 5,
4cb2536f 1670 Dialer, this, FtpStateData::ftpReadControlReply);
9e008dda 1671 comm_read(ctrl.fd, ctrl.buf + ctrl.offset, ctrl.size - ctrl.offset, reader);
62e76326 1672 /*
1673 * Cancel the timeout on the Data socket (if any) and
1674 * establish one on the control socket.
1675 */
1676
9e008dda
AJ
1677 if (data.fd > -1) {
1678 AsyncCall::Pointer nullCall = NULL;
dc56a9b1 1679 commSetTimeout(data.fd, -1, nullCall);
9e008dda 1680 }
dc56a9b1 1681
9e008dda 1682 typedef CommCbMemFunT<FtpStateData, CommTimeoutCbParams> TimeoutDialer;
4299f876 1683 AsyncCall::Pointer timeoutCall = JobCallback(9, 5,
4cb2536f 1684 TimeoutDialer, this, FtpStateData::ftpTimeout);
62e76326 1685
dc56a9b1 1686 commSetTimeout(ctrl.fd, Config.Timeout.read, timeoutCall);
4f310655 1687 }
1688}
1689
dc56a9b1 1690void FtpStateData::ftpReadControlReply(const CommIoCbParams &io)
3fdadc70 1691{
dc56a9b1 1692 debugs(9, 3, "ftpReadControlReply: FD " << io.fd << ", Read " << io.size << " bytes");
c4b7a5a9 1693
dc56a9b1 1694 if (io.size > 0) {
1695 kb_incr(&statCounter.server.all.kbytes_in, io.size);
1696 kb_incr(&statCounter.server.ftp.kbytes_in, io.size);
3c7568c7 1697 }
62e76326 1698
dc56a9b1 1699 if (io.flag == COMM_ERR_CLOSING)
c4b7a5a9 1700 return;
62e76326 1701
9bc73deb 1702 if (EBIT_TEST(entry->flags, ENTRY_ABORTED)) {
dc56a9b1 1703 abortTransaction("entry aborted during control reply read");
62e76326 1704 return;
9bc73deb 1705 }
62e76326 1706
dc56a9b1 1707 assert(ctrl.offset < ctrl.size);
62e76326 1708
dc56a9b1 1709 if (io.flag == COMM_OK && io.size > 0) {
1710 fd_bytes(io.fd, io.size, FD_READ);
ee1679df 1711 }
62e76326 1712
dc56a9b1 1713 if (io.flag != COMM_OK || io.size < 0) {
9e008dda
AJ
1714 debugs(50, ignoreErrno(io.xerrno) ? 3 : DBG_IMPORTANT,
1715 "ftpReadControlReply: read error: " << xstrerr(io.xerrno));
62e76326 1716
dc56a9b1 1717 if (ignoreErrno(io.xerrno)) {
1718 scheduleReadControlReply(0);
62e76326 1719 } else {
dc56a9b1 1720 failed(ERR_READ_ERROR, io.xerrno);
6f0aab86 1721 /* failed closes ctrl.fd and frees ftpState */
62e76326 1722 return;
1723 }
1724
1725 return;
3fdadc70 1726 }
62e76326 1727
dc56a9b1 1728 if (io.size == 0) {
62e76326 1729 if (entry->store_status == STORE_PENDING) {
dc56a9b1 1730 failed(ERR_FTP_FAILURE, 0);
6f0aab86 1731 /* failed closes ctrl.fd and frees ftpState */
62e76326 1732 return;
1733 }
1734
9e008dda 1735 /* XXX this may end up having to be serverComplete() .. */
dc56a9b1 1736 abortTransaction("zero control reply read");
62e76326 1737 return;
3fdadc70 1738 }
62e76326 1739
dc56a9b1 1740 unsigned int len =io.size + ctrl.offset;
1741 ctrl.offset = len;
1742 assert(len <= ctrl.size);
1743 handleControlReply();
4f310655 1744}
1745
6f0aab86 1746void
1747FtpStateData::handleControlReply()
4f310655 1748{
ec603b25 1749 wordlist **W;
47f6e231 1750 size_t bytes_used = 0;
6f0aab86 1751 wordlistDestroy(&ctrl.message);
1752 ctrl.message = ftpParseControlReply(ctrl.buf,
1753 ctrl.offset, &ctrl.replycode, &bytes_used);
62e76326 1754
6f0aab86 1755 if (ctrl.message == NULL) {
62e76326 1756 /* didn't get complete reply yet */
1757
47f6e231 1758 if (ctrl.offset == ctrl.size) {
6f0aab86 1759 ctrl.buf = (char *)memReallocBuf(ctrl.buf, ctrl.size << 1, &ctrl.size);
62e76326 1760 }
1761
6f0aab86 1762 scheduleReadControlReply(0);
62e76326 1763 return;
6f0aab86 1764 } else if (ctrl.offset == bytes_used) {
62e76326 1765 /* used it all up */
6f0aab86 1766 ctrl.offset = 0;
4f310655 1767 } else {
62e76326 1768 /* Got some data past the complete reply */
6f0aab86 1769 assert(bytes_used < ctrl.offset);
1770 ctrl.offset -= bytes_used;
1771 xmemmove(ctrl.buf, ctrl.buf + bytes_used,
1772 ctrl.offset);
3fdadc70 1773 }
62e76326 1774
858783c9 1775 /* Move the last line of the reply message to ctrl.last_reply */
3d0ac046 1776 for (W = &ctrl.message; (*W)->next; W = &(*W)->next);
6f0aab86 1777 safe_free(ctrl.last_reply);
62e76326 1778
6f0aab86 1779 ctrl.last_reply = xstrdup((*W)->key);
62e76326 1780
858783c9 1781 wordlistDestroy(W);
62e76326 1782
858783c9 1783 /* Copy the rest of the message to cwd_message to be printed in
1784 * error messages
1785 */
0477a072
AJ
1786 if (ctrl.message) {
1787 for (wordlist *w = ctrl.message; w; w = w->next) {
1788 cwd_message.append('\n');
1789 cwd_message.append(w->key);
1790 }
1791 }
62e76326 1792
a689bd4e 1793 debugs(9, 3, HERE << "state=" << state << ", code=" << ctrl.replycode);
62e76326 1794
6f0aab86 1795 FTP_SM_FUNCS[state] (this);
234967c9 1796}
1797
3fdadc70 1798/* ====================================================================== */
1799
63be0a78 1800/// \ingroup ServerProtocolFTPInternal
3fdadc70 1801static void
1802ftpReadWelcome(FtpStateData * ftpState)
1803{
1804 int code = ftpState->ctrl.replycode;
a689bd4e 1805 debugs(9, 3, HERE);
62e76326 1806
e55f0142 1807 if (ftpState->flags.pasv_only)
62e76326 1808 ftpState->login_att++;
1809
9bc73deb 1810 /* Dont retry if the FTP server accepted the connection */
b6b6f466 1811 ftpState->fwd->dontRetry(true);
62e76326 1812
3fdadc70 1813 if (code == 220) {
62e76326 1814 if (ftpState->ctrl.message) {
1815 if (strstr(ftpState->ctrl.message->key, "NetWare"))
1816 ftpState->flags.skip_whitespace = 1;
1817 }
1818
1819 ftpSendUser(ftpState);
cdc33f35 1820 } else if (code == 120) {
62e76326 1821 if (NULL != ftpState->ctrl.message)
a689bd4e 1822 debugs(9, DBG_IMPORTANT, "FTP server is busy: " << ftpState->ctrl.message->key);
62e76326 1823
1824 return;
3fdadc70 1825 } else {
62e76326 1826 ftpFail(ftpState);
3fdadc70 1827 }
1828}
1829
f381d699
AJ
1830/**
1831 * Translate FTP login failure into HTTP error
1832 * this is an attmpt to get the 407 message to show up outside Squid.
1833 * its NOT a general failure. But a correct FTP response type.
1834 */
1835void
1836FtpStateData::loginFailed()
1837{
1838 ErrorState *err = NULL;
1839 const char *command, *reply;
1840
1841 if (state == SENT_USER || state == SENT_PASS) {
1842 if (ctrl.replycode > 500) {
1843 if (password_url)
1844 err = errorCon(ERR_FTP_FORBIDDEN, HTTP_FORBIDDEN, fwd->request);
1845 else
1846 err = errorCon(ERR_FTP_FORBIDDEN, HTTP_UNAUTHORIZED, fwd->request);
1847 } else if (ctrl.replycode == 421) {
1848 err = errorCon(ERR_FTP_UNAVAILABLE, HTTP_SERVICE_UNAVAILABLE, fwd->request);
1849 }
b115733c
AJ
1850 }
1851
1852 if (err) {
f381d699
AJ
1853 ftpFail(this);
1854 return;
1855 }
1856
1857 err->ftp.server_msg = ctrl.message;
1858
1859 ctrl.message = NULL;
1860
1861 if (old_request)
1862 command = old_request;
1863 else
1864 command = ctrl.last_command;
1865
1866 if (command && strncmp(command, "PASS", 4) == 0)
1867 command = "PASS <yourpassword>";
1868
1869 if (old_reply)
1870 reply = old_reply;
1871 else
1872 reply = ctrl.last_reply;
1873
1874 if (command)
1875 err->ftp.request = xstrdup(command);
1876
1877 if (reply)
1878 err->ftp.reply = xstrdup(reply);
1879
1880
1881 HttpReply *newrep = err->BuildHttpReply();
1882 errorStateFree(err);
1883 /* add Authenticate header */
1884 newrep->header.putAuth("Basic", ftpRealm());
1885
1886 // add it to the store entry for response....
1887 entry->replaceHttpReply(newrep);
1888 serverComplete();
1889}
1890
1891const char *
1892FtpStateData::ftpRealm()
1893{
1894 static char realm[8192];
1895
1896 /* This request is not fully authenticated */
1897 if (!request) {
1898 snprintf(realm, 8192, "FTP %s unknown", user);
1899 } else if (request->port == 21) {
1900 snprintf(realm, 8192, "FTP %s %s", user, request->GetHost());
1901 } else {
1902 snprintf(realm, 8192, "FTP %s %s port %d", user, request->GetHost(), request->port);
1903 }
1904 return realm;
1905}
1906
63be0a78 1907/// \ingroup ServerProtocolFTPInternal
969c39b9 1908static void
1909ftpSendUser(FtpStateData * ftpState)
1910{
a11382aa 1911 /* check the server control channel is still available */
9e008dda 1912 if (!ftpState || !ftpState->haveControlChannel("ftpSendUser"))
a11382aa 1913 return;
1914
969c39b9 1915 if (ftpState->proxy_host != NULL)
62e76326 1916 snprintf(cbuf, 1024, "USER %s@%s\r\n",
1917 ftpState->user,
cc192b50 1918 ftpState->request->GetHost());
969c39b9 1919 else
62e76326 1920 snprintf(cbuf, 1024, "USER %s\r\n", ftpState->user);
1921
2f47fadf 1922 ftpState->writeCommand(cbuf);
62e76326 1923
969c39b9 1924 ftpState->state = SENT_USER;
1925}
1926
63be0a78 1927/// \ingroup ServerProtocolFTPInternal
3fdadc70 1928static void
1929ftpReadUser(FtpStateData * ftpState)
234967c9 1930{
3fdadc70 1931 int code = ftpState->ctrl.replycode;
a689bd4e 1932 debugs(9, 3, HERE);
62e76326 1933
3fdadc70 1934 if (code == 230) {
62e76326 1935 ftpReadPass(ftpState);
3fdadc70 1936 } else if (code == 331) {
62e76326 1937 ftpSendPass(ftpState);
3fdadc70 1938 } else {
f381d699 1939 ftpState->loginFailed();
3fdadc70 1940 }
1941}
1942
63be0a78 1943/// \ingroup ServerProtocolFTPInternal
969c39b9 1944static void
1945ftpSendPass(FtpStateData * ftpState)
1946{
a11382aa 1947 /* check the server control channel is still available */
9e008dda 1948 if (!ftpState || !ftpState->haveControlChannel("ftpSendPass"))
a11382aa 1949 return;
1950
969c39b9 1951 snprintf(cbuf, 1024, "PASS %s\r\n", ftpState->password);
2f47fadf 1952 ftpState->writeCommand(cbuf);
969c39b9 1953 ftpState->state = SENT_PASS;
1954}
1955
63be0a78 1956/// \ingroup ServerProtocolFTPInternal
3fdadc70 1957static void
1958ftpReadPass(FtpStateData * ftpState)
1959{
1960 int code = ftpState->ctrl.replycode;
4d6c56a6 1961 debugs(9, 3, HERE << "code=" << code);
62e76326 1962
3fdadc70 1963 if (code == 230) {
62e76326 1964 ftpSendType(ftpState);
3fdadc70 1965 } else {
f381d699 1966 ftpState->loginFailed();
3fdadc70 1967 }
1968}
1969
63be0a78 1970/// \ingroup ServerProtocolFTPInternal
969c39b9 1971static void
1972ftpSendType(FtpStateData * ftpState)
1973{
02922e76 1974 const char *t;
1975 const char *filename;
969c39b9 1976 char mode;
a11382aa 1977
1978 /* check the server control channel is still available */
9e008dda 1979 if (!ftpState || !ftpState->haveControlChannel("ftpSendType"))
a11382aa 1980 return;
1981
9e242e02 1982 /*
1983 * Ref section 3.2.2 of RFC 1738
1984 */
b02bfc2d 1985 mode = ftpState->typecode;
62e76326 1986
b02bfc2d 1987 switch (mode) {
62e76326 1988
9e242e02 1989 case 'D':
62e76326 1990 mode = 'A';
1991 break;
1992
9e242e02 1993 case 'A':
62e76326 1994
9e242e02 1995 case 'I':
62e76326 1996 break;
1997
9e242e02 1998 default:
62e76326 1999
2000 if (ftpState->flags.isdir) {
2001 mode = 'A';
2002 } else {
650c4b88 2003 t = ftpState->request->urlpath.rpos('/');
d53b3f6d 2004 filename = t ? t + 1 : ftpState->request->urlpath.termedBuf();
62e76326 2005 mode = mimeGetTransferMode(filename);
2006 }
2007
2008 break;
9e242e02 2009 }
62e76326 2010
969c39b9 2011 if (mode == 'I')
62e76326 2012 ftpState->flags.binary = 1;
cfbf5373 2013 else
62e76326 2014 ftpState->flags.binary = 0;
2015
969c39b9 2016 snprintf(cbuf, 1024, "TYPE %c\r\n", mode);
62e76326 2017
2f47fadf 2018 ftpState->writeCommand(cbuf);
62e76326 2019
969c39b9 2020 ftpState->state = SENT_TYPE;
2021}
2022
63be0a78 2023/// \ingroup ServerProtocolFTPInternal
3fdadc70 2024static void
2025ftpReadType(FtpStateData * ftpState)
2026{
2027 int code = ftpState->ctrl.replycode;
3fdadc70 2028 char *path;
6e5ae4a4 2029 char *d, *p;
a689bd4e 2030 debugs(9, 3, HERE);
62e76326 2031
3fdadc70 2032 if (code == 200) {
d53b3f6d 2033 p = path = xstrdup(ftpState->request->urlpath.termedBuf());
62e76326 2034
2035 if (*p == '/')
2036 p++;
2037
2038 while (*p) {
2039 d = p;
2040 p += strcspn(p, "/");
2041
2042 if (*p)
2043 *p++ = '\0';
2044
2045 rfc1738_unescape(d);
2046
80dc929d 2047 if (*d)
2048 wordlistAdd(&ftpState->pathcomps, d);
62e76326 2049 }
2050
2051 xfree(path);
2052
2053 if (ftpState->pathcomps)
2054 ftpTraverseDirectory(ftpState);
2055 else
2056 ftpListDir(ftpState);
3fdadc70 2057 } else {
62e76326 2058 ftpFail(ftpState);
3fdadc70 2059 }
2060}
2061
63be0a78 2062/// \ingroup ServerProtocolFTPInternal
3fdadc70 2063static void
969c39b9 2064ftpTraverseDirectory(FtpStateData * ftpState)
3fdadc70 2065{
2066 wordlist *w;
a689bd4e 2067 debugs(9, 4, HERE << (ftpState->filepath ? ftpState->filepath : "<NULL>"));
969c39b9 2068
5627a633 2069 safe_free(ftpState->dirpath);
2070 ftpState->dirpath = ftpState->filepath;
2071 ftpState->filepath = NULL;
2072
969c39b9 2073 /* Done? */
62e76326 2074
969c39b9 2075 if (ftpState->pathcomps == NULL) {
a689bd4e 2076 debugs(9, 3, HERE << "the final component was a directory");
62e76326 2077 ftpListDir(ftpState);
2078 return;
3fdadc70 2079 }
62e76326 2080
969c39b9 2081 /* Go to next path component */
2082 w = ftpState->pathcomps;
62e76326 2083
969c39b9 2084 ftpState->filepath = w->key;
62e76326 2085
969c39b9 2086 ftpState->pathcomps = w->next;
62e76326 2087
d295d770 2088 delete w;
62e76326 2089
969c39b9 2090 /* Check if we are to CWD or RETR */
e55f0142 2091 if (ftpState->pathcomps != NULL || ftpState->flags.isdir) {
62e76326 2092 ftpSendCwd(ftpState);
969c39b9 2093 } else {
a689bd4e 2094 debugs(9, 3, HERE << "final component is probably a file");
62e76326 2095 ftpGetFile(ftpState);
2096 return;
969c39b9 2097 }
2098}
2099
63be0a78 2100/// \ingroup ServerProtocolFTPInternal
969c39b9 2101static void
2102ftpSendCwd(FtpStateData * ftpState)
2103{
9ededfe2 2104 char *path = NULL;
a11382aa 2105
2106 /* check the server control channel is still available */
9e008dda 2107 if (!ftpState || !ftpState->haveControlChannel("ftpSendCwd"))
a11382aa 2108 return;
2109
a689bd4e 2110 debugs(9, 3, HERE);
62e76326 2111
9ededfe2 2112 path = ftpState->filepath;
2113
969c39b9 2114 if (!strcmp(path, "..") || !strcmp(path, "/")) {
62e76326 2115 ftpState->flags.no_dotdot = 1;
13e80e5b 2116 } else {
62e76326 2117 ftpState->flags.no_dotdot = 0;
13e80e5b 2118 }
62e76326 2119
80dc929d 2120 snprintf(cbuf, 1024, "CWD %s\r\n", path);
62e76326 2121
2f47fadf 2122 ftpState->writeCommand(cbuf);
62e76326 2123
969c39b9 2124 ftpState->state = SENT_CWD;
3fdadc70 2125}
77a30ebb 2126
63be0a78 2127/// \ingroup ServerProtocolFTPInternal
3fdadc70 2128static void
2129ftpReadCwd(FtpStateData * ftpState)
2130{
2131 int code = ftpState->ctrl.replycode;
a689bd4e 2132 debugs(9, 3, HERE);
62e76326 2133
3fdadc70 2134 if (code >= 200 && code < 300) {
62e76326 2135 /* CWD OK */
6f0aab86 2136 ftpState->unhack();
62e76326 2137
0477a072
AJ
2138 /* Reset cwd_message to only include the last message */
2139 ftpState->cwd_message.reset("");
2140 for (wordlist *w = ftpState->ctrl.message; w; w = w->next) {
2141 ftpState->cwd_message.append(' ');
2142 ftpState->cwd_message.append(w->key);
2143 }
62e76326 2144 ftpState->ctrl.message = NULL;
2145
2146 /* Continue to traverse the path */
2147 ftpTraverseDirectory(ftpState);
3fdadc70 2148 } else {
62e76326 2149 /* CWD FAILED */
2150
2151 if (!ftpState->flags.put)
2152 ftpFail(ftpState);
2153 else
2154 ftpSendMkdir(ftpState);
c021888f 2155 }
3fdadc70 2156}
2157
63be0a78 2158/// \ingroup ServerProtocolFTPInternal
54220df8 2159static void
94439e4e 2160ftpSendMkdir(FtpStateData * ftpState)
54220df8 2161{
9ededfe2 2162 char *path = NULL;
a11382aa 2163
2164 /* check the server control channel is still available */
9e008dda 2165 if (!ftpState || !ftpState->haveControlChannel("ftpSendMkdir"))
a11382aa 2166 return;
2167
9ededfe2 2168 path = ftpState->filepath;
a689bd4e 2169 debugs(9, 3, HERE << "with path=" << path);
4162ee3b 2170 snprintf(cbuf, 1024, "MKD %s\r\n", path);
2f47fadf 2171 ftpState->writeCommand(cbuf);
4162ee3b 2172 ftpState->state = SENT_MKDIR;
54220df8 2173}
2174
63be0a78 2175/// \ingroup ServerProtocolFTPInternal
54220df8 2176static void
4162ee3b 2177ftpReadMkdir(FtpStateData * ftpState)
2178{
2179 char *path = ftpState->filepath;
2180 int code = ftpState->ctrl.replycode;
2181
a689bd4e 2182 debugs(9, 3, HERE << "path " << path << ", code " << code);
62e76326 2183
4162ee3b 2184 if (code == 257) { /* success */
62e76326 2185 ftpSendCwd(ftpState);
4162ee3b 2186 } else if (code == 550) { /* dir exists */
62e76326 2187
2188 if (ftpState->flags.put_mkdir) {
2189 ftpState->flags.put_mkdir = 1;
2190 ftpSendCwd(ftpState);
2191 } else
2192 ftpSendReply(ftpState);
4162ee3b 2193 } else
62e76326 2194 ftpSendReply(ftpState);
54220df8 2195}
2196
63be0a78 2197/// \ingroup ServerProtocolFTPInternal
dbfed404 2198static void
2199ftpGetFile(FtpStateData * ftpState)
2200{
2201 assert(*ftpState->filepath != '\0');
e55f0142 2202 ftpState->flags.isdir = 0;
dbfed404 2203 ftpSendMdtm(ftpState);
2204}
2205
63be0a78 2206/// \ingroup ServerProtocolFTPInternal
dbfed404 2207static void
2208ftpListDir(FtpStateData * ftpState)
2209{
d5f80edc 2210 if (ftpState->flags.dir_slash) {
a689bd4e 2211 debugs(9, 3, HERE << "Directory path did not end in /");
62e76326 2212 ftpState->title_url.append("/");
2213 ftpState->flags.isdir = 1;
dbfed404 2214 }
62e76326 2215
a689bd4e 2216 ftpSendPassive(ftpState);
dbfed404 2217}
2218
63be0a78 2219/// \ingroup ServerProtocolFTPInternal
969c39b9 2220static void
2221ftpSendMdtm(FtpStateData * ftpState)
2222{
a11382aa 2223 /* check the server control channel is still available */
9e008dda 2224 if (!ftpState || !ftpState->haveControlChannel("ftpSendMdtm"))
a11382aa 2225 return;
2226
969c39b9 2227 assert(*ftpState->filepath != '\0');
2228 snprintf(cbuf, 1024, "MDTM %s\r\n", ftpState->filepath);
2f47fadf 2229 ftpState->writeCommand(cbuf);
969c39b9 2230 ftpState->state = SENT_MDTM;
2231}
2232
63be0a78 2233/// \ingroup ServerProtocolFTPInternal
3fdadc70 2234static void
2235ftpReadMdtm(FtpStateData * ftpState)
2236{
2237 int code = ftpState->ctrl.replycode;
a689bd4e 2238 debugs(9, 3, HERE);
62e76326 2239
3fdadc70 2240 if (code == 213) {
62e76326 2241 ftpState->mdtm = parse_iso3307_time(ftpState->ctrl.last_reply);
6f0aab86 2242 ftpState->unhack();
3fdadc70 2243 } else if (code < 0) {
62e76326 2244 ftpFail(ftpState);
9e008dda 2245 return;
77a30ebb 2246 }
62e76326 2247
969c39b9 2248 ftpSendSize(ftpState);
2249}
2250
63be0a78 2251/// \ingroup ServerProtocolFTPInternal
969c39b9 2252static void
2253ftpSendSize(FtpStateData * ftpState)
2254{
a11382aa 2255 /* check the server control channel is still available */
9e008dda 2256 if (!ftpState || !ftpState->haveControlChannel("ftpSendSize"))
a11382aa 2257 return;
2258
969c39b9 2259 /* Only send SIZE for binary transfers. The returned size
2260 * is useless on ASCII transfers */
62e76326 2261
e55f0142 2262 if (ftpState->flags.binary) {
62e76326 2263 assert(ftpState->filepath != NULL);
2264 assert(*ftpState->filepath != '\0');
2265 snprintf(cbuf, 1024, "SIZE %s\r\n", ftpState->filepath);
2f47fadf 2266 ftpState->writeCommand(cbuf);
62e76326 2267 ftpState->state = SENT_SIZE;
969c39b9 2268 } else
62e76326 2269 /* Skip to next state no non-binary transfers */
a689bd4e 2270 ftpSendPassive(ftpState);
3fdadc70 2271}
2272
63be0a78 2273/// \ingroup ServerProtocolFTPInternal
3fdadc70 2274static void
2275ftpReadSize(FtpStateData * ftpState)
2276{
2277 int code = ftpState->ctrl.replycode;
a689bd4e 2278 debugs(9, 3, HERE);
62e76326 2279
3fdadc70 2280 if (code == 213) {
6f0aab86 2281 ftpState->unhack();
47f6e231 2282 ftpState->theSize = strtoll(ftpState->ctrl.last_reply, NULL, 10);
62e76326 2283
47f6e231 2284 if (ftpState->theSize == 0) {
a689bd4e 2285 debugs(9, 2, "SIZE reported " <<
9e008dda 2286 ftpState->ctrl.last_reply << " on " <<
d53b3f6d 2287 ftpState->title_url);
47f6e231 2288 ftpState->theSize = -1;
62e76326 2289 }
3fdadc70 2290 } else if (code < 0) {
62e76326 2291 ftpFail(ftpState);
9e008dda 2292 return;
3fdadc70 2293 }
62e76326 2294
a689bd4e 2295 ftpSendPassive(ftpState);
3fdadc70 2296}
2297
63be0a78 2298/**
2299 \ingroup ServerProtocolFTPInternal
2300 */
cc192b50 2301static void
a689bd4e 2302ftpReadEPSV(FtpStateData* ftpState)
cc192b50 2303{
a689bd4e 2304 int code = ftpState->ctrl.replycode;
2305 char h1, h2, h3, h4;
2306 int n;
2307 u_short port;
b7ac5457 2308 Ip::Address ipa_remote;
a689bd4e 2309 int fd = ftpState->data.fd;
2310 char *buf;
2311 debugs(9, 3, HERE);
2312
2313 if (code != 229 && code != 522) {
9e008dda 2314 if (code == 200) {
a689bd4e 2315 /* handle broken servers (RFC 2428 says OK code for EPSV MUST be 229 not 200) */
2316 /* vsftpd for one send '200 EPSV ALL ok.' without even port info.
2317 * Its okay to re-send EPSV 1/2 but nothing else. */
2318 debugs(9, DBG_IMPORTANT, "Broken FTP Server at " << fd_table[ftpState->ctrl.fd].ipaddr << ". Wrong accept code for EPSV");
9e008dda 2319 } else {
a689bd4e 2320 debugs(9, 2, "EPSV not supported by remote end");
2321 ftpState->state = SENT_EPSV_1; /* simulate having failed EPSV 1 (last EPSV to try before shifting to PASV) */
2322 }
2323 ftpSendPassive(ftpState);
2324 return;
2325 }
cc192b50 2326
9e008dda 2327 if (code == 522) {
a689bd4e 2328 /* server response with list of supported methods */
2329 /* 522 Network protocol not supported, use (1) */
2330 /* 522 Network protocol not supported, use (1,2) */
055421ee 2331 /* 522 Network protocol not supported, use (2) */
e1381638 2332 /* TODO: handle the (1,2) case. We might get it back after EPSV ALL
0f738a43 2333 * which means close data + control without self-destructing and re-open from scratch. */
a689bd4e 2334 debugs(9, 5, HERE << "scanning: " << ftpState->ctrl.last_reply);
0f738a43
AJ
2335 buf = ftpState->ctrl.last_reply;
2336 while (buf != NULL && *buf != '\0' && *buf != '\n' && *buf != '(') ++buf;
2337 if (buf != NULL && *buf == '\n') ++buf;
cc192b50 2338
9e008dda 2339 if (buf == NULL || *buf == '\0') {
a689bd4e 2340 /* handle broken server (RFC 2428 says MUST specify supported protocols in 522) */
2341 debugs(9, DBG_IMPORTANT, "Broken FTP Server at " << fd_table[ftpState->ctrl.fd].ipaddr << ". 522 error missing protocol negotiation hints");
2342 ftpSendPassive(ftpState);
9e008dda 2343 } else if (strcmp(buf, "(1)") == 0) {
a689bd4e 2344 ftpState->state = SENT_EPSV_2; /* simulate having sent and failed EPSV 2 */
2345 ftpSendPassive(ftpState);
9e008dda 2346 } else if (strcmp(buf, "(2)") == 0) {
055421ee
AJ
2347 if (Ip::EnableIpv6) {
2348 /* If server only supports EPSV 2 and we have already tried that. Go straight to EPRT */
2349 if (ftpState->state == SENT_EPSV_2) {
2350 ftpSendEPRT(ftpState);
2351 } else {
2352 /* or try the next Passive mode down the chain. */
2353 ftpSendPassive(ftpState);
2354 }
9e008dda 2355 } else {
055421ee
AJ
2356 /* Server only accept EPSV in IPv6 traffic. */
2357 ftpState->state = SENT_EPSV_1; /* simulate having sent and failed EPSV 1 */
a689bd4e 2358 ftpSendPassive(ftpState);
2359 }
e1381638 2360 } else {
0f738a43
AJ
2361 /* handle broken server (RFC 2428 says MUST specify supported protocols in 522) */
2362 debugs(9, DBG_IMPORTANT, "WARNING: Server at " << fd_table[ftpState->ctrl.fd].ipaddr << " sent unknown protocol negotiation hint: " << buf);
2363 ftpSendPassive(ftpState);
2364 }
a689bd4e 2365 return;
2366 }
2367
2368 /* 229 Entering Extended Passive Mode (|||port|) */
2369 /* ANSI sez [^0-9] is undefined, it breaks on Watcom cc */
2370 debugs(9, 5, "scanning: " << ftpState->ctrl.last_reply);
2371
2372 buf = ftpState->ctrl.last_reply + strcspn(ftpState->ctrl.last_reply, "(");
2373
2374 n = sscanf(buf, "(%c%c%c%hu%c)", &h1, &h2, &h3, &port, &h4);
2375
2376 if (h1 != h2 || h1 != h3 || h1 != h4) {
2377 debugs(9, DBG_IMPORTANT, "Invalid EPSV reply from " <<
2378 fd_table[ftpState->ctrl.fd].ipaddr << ": " <<
2379 ftpState->ctrl.last_reply);
2380
2381 ftpSendPassive(ftpState);
2382 return;
2383 }
2384
2385 if (0 == port) {
2386 debugs(9, DBG_IMPORTANT, "Unsafe EPSV reply from " <<
2387 fd_table[ftpState->ctrl.fd].ipaddr << ": " <<
2388 ftpState->ctrl.last_reply);
2389
2390 ftpSendPassive(ftpState);
2391 return;
2392 }
2393
2394 if (Config.Ftp.sanitycheck) {
2395 if (port < 1024) {
2396 debugs(9, DBG_IMPORTANT, "Unsafe EPSV reply from " <<
2397 fd_table[ftpState->ctrl.fd].ipaddr << ": " <<
2398 ftpState->ctrl.last_reply);
2399
2400 ftpSendPassive(ftpState);
2401 return;
2402 }
2403 }
2404
2405 ftpState->data.port = port;
2406
2407 ftpState->data.host = xstrdup(fd_table[ftpState->ctrl.fd].ipaddr);
2408
2409 safe_free(ftpState->ctrl.last_command);
2410
2411 safe_free(ftpState->ctrl.last_reply);
2412
2413 ftpState->ctrl.last_command = xstrdup("Connect to server data port");
2414
2415 debugs(9, 3, HERE << "connecting to " << ftpState->data.host << ", port " << ftpState->data.port);
cc192b50 2416
a689bd4e 2417 commConnectStart(fd, ftpState->data.host, port, FtpStateData::ftpPasvCallback, ftpState);
2418}
cc192b50 2419
63be0a78 2420/** \ingroup ServerProtocolFTPInternal
2421 *
a689bd4e 2422 * Send Passive connection request.
2423 * Default method is to use modern EPSV request.
2424 * The failover mechanism should check for previous state and re-call with alternates on failure.
2425 */
3fdadc70 2426static void
a689bd4e 2427ftpSendPassive(FtpStateData * ftpState)
3fdadc70 2428{
b7ac5457 2429 Ip::Address addr;
cc192b50 2430 struct addrinfo *AI = NULL;
62e76326 2431
a689bd4e 2432 /** Checks the server control channel is still available before running. */
9e008dda 2433 if (!ftpState || !ftpState->haveControlChannel("ftpSendPassive"))
a11382aa 2434 return;
2435
a689bd4e 2436 debugs(9, 3, HERE);
2437
2438 /** \par
2439 * Checks for EPSV ALL special conditions:
2440 * If enabled to be sent, squid MUST NOT request any other connect methods.
2441 * If 'ALL' is sent and fails the entire FTP Session fails.
2442 * NP: By my reading exact EPSV protocols maybe attempted, but only EPSV method. */
9e008dda 2443 if (Config.Ftp.epsv_all && ftpState->flags.epsv_all_sent && ftpState->state == SENT_EPSV_1 ) {
a689bd4e 2444 debugs(9, DBG_IMPORTANT, "FTP does not allow PASV method after 'EPSV ALL' has been sent.");
2445 ftpFail(ftpState);
2446 return;
2447 }
ee65546f 2448
a689bd4e 2449 /** \par
2450 * Checks for 'HEAD' method request and passes off for special handling by FtpStateData::processHeadResponse(). */
47f6e231 2451 if (ftpState->request->method == METHOD_HEAD && (ftpState->flags.isdir || ftpState->theSize != -1)) {
063a47b5 2452 ftpState->processHeadResponse(); // may call serverComplete
62e76326 2453 return;
7497ceef 2454 }
62e76326 2455
94b88585
AR
2456 /// Closes any old FTP-Data connection which may exist. */
2457 ftpState->data.close();
62e76326 2458
a689bd4e 2459 /** \par
2460 * Checks for previous EPSV/PASV failures on this server/session.
2461 * Diverts to EPRT immediately if they are not working. */
e55f0142 2462 if (!ftpState->flags.pasv_supported) {
cc192b50 2463 ftpSendEPRT(ftpState);
62e76326 2464 return;
30a4f2a8 2465 }
62e76326 2466
a689bd4e 2467 /** \par
2468 * Locates the Address of the remote server. */
cc192b50 2469 addr.InitAddrInfo(AI);
62e76326 2470
cc192b50 2471 if (getsockname(ftpState->ctrl.fd, AI->ai_addr, &AI->ai_addrlen)) {
a689bd4e 2472 /** If it cannot be located the FTP Session is killed. */
cc192b50 2473 addr.FreeAddrInfo(AI);
a689bd4e 2474 debugs(9, DBG_CRITICAL, HERE << "getsockname(" << ftpState->ctrl.fd << ",'" << addr << "',...): " << xstrerror());
62e76326 2475 ftpFail(ftpState);
2476 return;
cdc33f35 2477 }
62e76326 2478
cc192b50 2479 addr = *AI;
cc192b50 2480 addr.FreeAddrInfo(AI);
2481
a689bd4e 2482 /** \par
2483 * Send EPSV (ALL,2,1) or PASV on the control channel.
2484 *
d85b8894 2485 * - EPSV ALL is used if enabled.
9adb5bc5
AJ
2486 * - EPSV 2 is used if ALL is disabled and IPv6 is available and ctrl channel is IPv6.
2487 * - EPSV 1 is used if EPSV 2 (IPv6) fails or is not available or ctrl channel is IPv4.
d85b8894 2488 * - PASV is used if EPSV 1 fails.
a689bd4e 2489 */
9e008dda 2490 switch (ftpState->state) {
9adb5bc5
AJ
2491 case SENT_EPSV_ALL: /* EPSV ALL resulted in a bad response. Try ther EPSV methods. */
2492 ftpState->flags.epsv_all_sent = true;
2493 if (addr.IsIPv6()) {
fe2d668b 2494 debugs(9, 5, HERE << "FTP Channel is IPv6 (" << addr << ") attempting EPSV 2 after EPSV ALL has failed.");
9adb5bc5
AJ
2495 snprintf(cbuf, 1024, "EPSV 2\r\n");
2496 ftpState->state = SENT_EPSV_2;
2497 break;
2498 }
2499 // else fall through to skip EPSV 2
62e76326 2500
a689bd4e 2501 case SENT_EPSV_2: /* EPSV IPv6 failed. Try EPSV IPv4 */
9adb5bc5 2502 if (addr.IsIPv4()) {
fe2d668b 2503 debugs(9, 5, HERE << "FTP Channel is IPv4 (" << addr << ") attempting EPSV 1 after EPSV ALL has failed.");
9adb5bc5
AJ
2504 snprintf(cbuf, 1024, "EPSV 1\r\n");
2505 ftpState->state = SENT_EPSV_1;
d06d0e1e 2506 break;
e1381638 2507 } else if (ftpState->flags.epsv_all_sent) {
9adb5bc5
AJ
2508 debugs(9, DBG_IMPORTANT, "FTP does not allow PASV method after 'EPSV ALL' has been sent.");
2509 ftpFail(ftpState);
2510 return;
2511 }
2512 // else fall through to skip EPSV 1
a689bd4e 2513
9adb5bc5 2514 case SENT_EPSV_1: /* EPSV options exhausted. Try PASV now. */
fe2d668b 2515 debugs(9, 5, HERE << "FTP Channel (" << addr << ") rejects EPSV connection attempts. Trying PASV instead.");
9adb5bc5
AJ
2516 snprintf(cbuf, 1024, "PASV\r\n");
2517 ftpState->state = SENT_PASV;
a689bd4e 2518 break;
2519
2520 default:
51ee534d 2521 if (!Config.Ftp.epsv) {
fe2d668b 2522 debugs(9, 5, HERE << "EPSV support manually disabled. Sending PASV for FTP Channel (" << addr <<")");
51ee534d
AJ
2523 snprintf(cbuf, 1024, "PASV\r\n");
2524 ftpState->state = SENT_PASV;
2525 } else if (Config.Ftp.epsv_all) {
fe2d668b 2526 debugs(9, 5, HERE << "EPSV ALL manually enabled. Attempting with FTP Channel (" << addr <<")");
a689bd4e 2527 snprintf(cbuf, 1024, "EPSV ALL\r\n");
2528 ftpState->state = SENT_EPSV_ALL;
2529 /* block other non-EPSV connections being attempted */
2530 ftpState->flags.epsv_all_sent = true;
9e008dda 2531 } else {
fe2d668b
AJ
2532 if (addr.IsIPv6()) {
2533 debugs(9, 5, HERE << "FTP Channel (" << addr << "). Sending default EPSV 2");
2534 snprintf(cbuf, 1024, "EPSV 2\r\n");
2535 ftpState->state = SENT_EPSV_2;
2536 }
fe2d668b
AJ
2537 if (addr.IsIPv4()) {
2538 debugs(9, 5, HERE << "Channel (" << addr <<"). Sending default EPSV 1");
2539 snprintf(cbuf, 1024, "EPSV 1\r\n");
2540 ftpState->state = SENT_EPSV_1;
2541 }
a689bd4e 2542 }
2543 break;
2544 }
62e76326 2545
9adb5bc5
AJ
2546 /** Otherwise, Open data channel with the same local address as control channel (on a new random port!) */
2547 addr.SetPort(0);
2548 int fd = comm_open(SOCK_STREAM,
2549 IPPROTO_TCP,
2550 addr,
2551 COMM_NONBLOCKING,
2552 ftpState->entry->url());
2553
fe2d668b 2554 debugs(9, 3, HERE << "Unconnected data socket created on FD " << fd << " from " << addr);
9adb5bc5
AJ
2555
2556 if (fd < 0) {
2557 ftpFail(ftpState);
2558 return;
2559 }
2560
2561 ftpState->data.opened(fd, ftpState->dataCloser());
a689bd4e 2562 ftpState->writeCommand(cbuf);
62e76326 2563
7e3ce7b9 2564 /*
2565 * ugly hack for ftp servers like ftp.netscape.com that sometimes
1be4874e 2566 * dont acknowledge PASV commands.
7e3ce7b9 2567 */
dc56a9b1 2568 typedef CommCbMemFunT<FtpStateData, CommTimeoutCbParams> TimeoutDialer;
4299f876 2569 AsyncCall::Pointer timeoutCall = JobCallback(9, 5,
4cb2536f 2570 TimeoutDialer, ftpState, FtpStateData::ftpTimeout);
dc56a9b1 2571
2572 commSetTimeout(ftpState->data.fd, 15, timeoutCall);
3fdadc70 2573}
2574
063a47b5 2575void
2576FtpStateData::processHeadResponse()
2577{
2578 debugs(9, 5, HERE << "handling HEAD response");
2579 ftpSendQuit(this);
2580 appendSuccessHeader();
2581
2582 /*
2583 * On rare occasions I'm seeing the entry get aborted after
2584 * ftpReadControlReply() and before here, probably when
2585 * trying to write to the client.
2586 */
2587 if (EBIT_TEST(entry->flags, ENTRY_ABORTED)) {
2588 abortTransaction("entry aborted while processing HEAD");
2589 return;
2590 }
2591
a83c6ed6
AR
2592#if USE_ADAPTATION
2593 if (adaptationAccessCheckPending) {
2594 debugs(9,3, HERE << "returning due to adaptationAccessCheckPending");
063a47b5 2595 return;
2596 }
2597#endif
2598
2599 // processReplyBody calls serverComplete() since there is no body
9e008dda 2600 processReplyBody();
063a47b5 2601}
2602
63be0a78 2603/// \ingroup ServerProtocolFTPInternal
3fdadc70 2604static void
2605ftpReadPasv(FtpStateData * ftpState)
2606{
2607 int code = ftpState->ctrl.replycode;
2608 int h1, h2, h3, h4;
2609 int p1, p2;
2610 int n;
2611 u_short port;
b7ac5457 2612 Ip::Address ipa_remote;
3fdadc70 2613 int fd = ftpState->data.fd;
748f6a15 2614 char *buf;
00c5afca 2615 LOCAL_ARRAY(char, ipaddr, 1024);
a689bd4e 2616 debugs(9, 3, HERE);
62e76326 2617
3fdadc70 2618 if (code != 227) {
a689bd4e 2619 debugs(9, 2, "PASV not supported by remote end");
cc192b50 2620 ftpSendEPRT(ftpState);
62e76326 2621 return;
3fdadc70 2622 }
62e76326 2623
748f6a15 2624 /* 227 Entering Passive Mode (h1,h2,h3,h4,p1,p2). */
2625 /* ANSI sez [^0-9] is undefined, it breaks on Watcom cc */
a689bd4e 2626 debugs(9, 5, HERE << "scanning: " << ftpState->ctrl.last_reply);
62e76326 2627
3f9d0b2d 2628 buf = ftpState->ctrl.last_reply + strcspn(ftpState->ctrl.last_reply, "0123456789");
62e76326 2629
748f6a15 2630 n = sscanf(buf, "%d,%d,%d,%d,%d,%d", &h1, &h2, &h3, &h4, &p1, &p2);
62e76326 2631
748f6a15 2632 if (n != 6 || p1 < 0 || p2 < 0 || p1 > 255 || p2 > 255) {
a689bd4e 2633 debugs(9, DBG_IMPORTANT, "Unsafe PASV reply from " <<
bf8fe701 2634 fd_table[ftpState->ctrl.fd].ipaddr << ": " <<
2635 ftpState->ctrl.last_reply);
2636
cc192b50 2637 ftpSendEPRT(ftpState);
62e76326 2638 return;
3fdadc70 2639 }
62e76326 2640
00c5afca 2641 snprintf(ipaddr, 1024, "%d.%d.%d.%d", h1, h2, h3, h4);
62e76326 2642
cc192b50 2643 ipa_remote = ipaddr;
2644
9e008dda 2645 if ( ipa_remote.IsAnyAddr() ) {
a689bd4e 2646 debugs(9, DBG_IMPORTANT, "Unsafe PASV reply from " <<
bf8fe701 2647 fd_table[ftpState->ctrl.fd].ipaddr << ": " <<
2648 ftpState->ctrl.last_reply);
2649
cc192b50 2650 ftpSendEPRT(ftpState);
62e76326 2651 return;
3fdadc70 2652 }
62e76326 2653
3fdadc70 2654 port = ((p1 << 8) + p2);
62e76326 2655
7f74df3a 2656 if (0 == port) {
a689bd4e 2657 debugs(9, DBG_IMPORTANT, "Unsafe PASV reply from " <<
bf8fe701 2658 fd_table[ftpState->ctrl.fd].ipaddr << ": " <<
2659 ftpState->ctrl.last_reply);
2660
cc192b50 2661 ftpSendEPRT(ftpState);
62e76326 2662 return;
7f74df3a 2663 }
62e76326 2664
00c5afca 2665 if (Config.Ftp.sanitycheck) {
62e76326 2666 if (port < 1024) {
a689bd4e 2667 debugs(9, DBG_IMPORTANT, "Unsafe PASV reply from " <<
bf8fe701 2668 fd_table[ftpState->ctrl.fd].ipaddr << ": " <<
2669 ftpState->ctrl.last_reply);
2670
cc192b50 2671 ftpSendEPRT(ftpState);
62e76326 2672 return;
2673 }
00c5afca 2674 }
62e76326 2675
9b312a19 2676 ftpState->data.port = port;
3f9d0b2d 2677
2678 if (Config.Ftp.sanitycheck)
2679 ftpState->data.host = xstrdup(fd_table[ftpState->ctrl.fd].ipaddr);
2680 else
2681 ftpState->data.host = xstrdup(ipaddr);
2682
9bc73deb 2683 safe_free(ftpState->ctrl.last_command);
3f9d0b2d 2684
9bc73deb 2685 safe_free(ftpState->ctrl.last_reply);
3f9d0b2d 2686
9bc73deb 2687 ftpState->ctrl.last_command = xstrdup("Connect to server data port");
3f9d0b2d 2688
a689bd4e 2689 debugs(9, 3, HERE << "connecting to " << ftpState->data.host << ", port " << ftpState->data.port);
3f9d0b2d 2690
6f0aab86 2691 commConnectStart(fd, ipaddr, port, FtpStateData::ftpPasvCallback, ftpState);
3fdadc70 2692}
2693
6f0aab86 2694void
3ff65596 2695FtpStateData::ftpPasvCallback(int fd, const DnsLookupDetails &dns, comm_err_t status, int xerrno, void *data)
3fdadc70 2696{
e6ccf245 2697 FtpStateData *ftpState = (FtpStateData *)data;
a689bd4e 2698 debugs(9, 3, HERE);
3ff65596 2699 ftpState->request->recordLookup(dns);
62e76326 2700
9b312a19 2701 if (status != COMM_OK) {
a689bd4e 2702 debugs(9, 2, HERE << "Failed to connect. Retrying without PASV.");
b6b6f466 2703 ftpState->fwd->dontRetry(false); /* this is a retryable error */
2704 ftpState->fwd->ftpPasvFailed(true);
8043ea13 2705 ftpState->failed(ERR_NONE, 0);
6f0aab86 2706 /* failed closes ctrl.fd and frees ftpState */
62e76326 2707 return;
3fdadc70 2708 }
62e76326 2709
3fdadc70 2710 ftpRestOrList(ftpState);
2711}
2712
63be0a78 2713/// \ingroup ServerProtocolFTPInternal
cdc33f35 2714static int
2715ftpOpenListenSocket(FtpStateData * ftpState, int fallback)
2716{
2717 int fd;
b7ac5457 2718 Ip::Address addr;
cc192b50 2719 struct addrinfo *AI = NULL;
cdc33f35 2720 int on = 1;
cc192b50 2721 int x = 0;
62e76326 2722
04f55905 2723 /// Close old data channels, if any. We may open a new one below.
94b88585 2724 ftpState->data.close();
62e76326 2725
4f310655 2726 /*
2727 * Set up a listen socket on the same local address as the
2728 * control connection.
2729 */
62e76326 2730
cc192b50 2731 addr.InitAddrInfo(AI);
2732
2733 x = getsockname(ftpState->ctrl.fd, AI->ai_addr, &AI->ai_addrlen);
2734
2735 addr = *AI;
2736
2737 addr.FreeAddrInfo(AI);
2738
9e008dda 2739 if (x) {
a689bd4e 2740 debugs(9, DBG_CRITICAL, HERE << "getsockname(" << ftpState->ctrl.fd << ",..): " << xstrerror());
62e76326 2741 return -1;
cdc33f35 2742 }
62e76326 2743
4f310655 2744 /*
2745 * REUSEADDR is needed in fallback mode, since the same port is
2746 * used for both control and data.
cdc33f35 2747 */
2748 if (fallback) {
62e76326 2749 setsockopt(ftpState->ctrl.fd, SOL_SOCKET, SO_REUSEADDR, (char *) &on, sizeof(on));
9e008dda 2750 } else {
a689bd4e 2751 /* if not running in fallback mode a new port needs to be retrieved */
2752 addr.SetPort(0);
2753 }
62e76326 2754
cdc33f35 2755 fd = comm_open(SOCK_STREAM,
bdb741f4 2756 IPPROTO_TCP,
cc192b50 2757 addr,
62e76326 2758 COMM_NONBLOCKING | (fallback ? COMM_REUSEADDR : 0),
3900307b 2759 ftpState->entry->url());
a689bd4e 2760 debugs(9, 3, HERE << "Unconnected data socket created on FD " << fd );
62e76326 2761
cdc33f35 2762 if (fd < 0) {
a689bd4e 2763 debugs(9, DBG_CRITICAL, HERE << "comm_open failed");
62e76326 2764 return -1;
cdc33f35 2765 }
62e76326 2766
04f55905 2767 typedef CommCbMemFunT<FtpStateData, CommAcceptCbParams> acceptDialer;
4299f876
AR
2768 AsyncCall::Pointer acceptCall = JobCallback(11, 5,
2769 acceptDialer, ftpState, FtpStateData::ftpAcceptDataConnection);
04f55905
AJ
2770 ftpState->data.listener = new Comm::ListenStateData(fd, acceptCall, false);
2771
1fc32b95 2772 if (!ftpState->data.listener || ftpState->data.listener->errcode != 0) {
62e76326 2773 comm_close(fd);
2774 return -1;
cdc33f35 2775 }
62e76326 2776
94b88585 2777 ftpState->data.opened(fd, ftpState->dataCloser());
d20b1cd0 2778 ftpState->data.port = comm_local_port(fd);
cdc33f35 2779 ftpState->data.host = NULL;
2780 return fd;
2781}
2782
63be0a78 2783/// \ingroup ServerProtocolFTPInternal
3fdadc70 2784static void
cc192b50 2785ftpSendPORT(FtpStateData * ftpState)
3fdadc70 2786{
cdc33f35 2787 int fd;
b7ac5457 2788 Ip::Address ipa;
cc192b50 2789 struct addrinfo *AI = NULL;
cdc33f35 2790 unsigned char *addrptr;
2791 unsigned char *portptr;
a11382aa 2792
2793 /* check the server control channel is still available */
9e008dda 2794 if (!ftpState || !ftpState->haveControlChannel("ftpSendPort"))
a11382aa 2795 return;
2796
9e008dda 2797 if (Config.Ftp.epsv_all && ftpState->flags.epsv_all_sent) {
a689bd4e 2798 debugs(9, DBG_IMPORTANT, "FTP does not allow PORT method after 'EPSV ALL' has been sent.");
2799 return;
2800 }
2801
2802 debugs(9, 3, HERE);
e55f0142 2803 ftpState->flags.pasv_supported = 0;
cdc33f35 2804 fd = ftpOpenListenSocket(ftpState, 0);
cc192b50 2805 ipa.InitAddrInfo(AI);
62e76326 2806
cc192b50 2807 if (getsockname(fd, AI->ai_addr, &AI->ai_addrlen)) {
2808 ipa.FreeAddrInfo(AI);
a689bd4e 2809 debugs(9, DBG_CRITICAL, HERE << "getsockname(" << fd << ",..): " << xstrerror());
bf8fe701 2810
62e76326 2811 /* XXX Need to set error message */
2812 ftpFail(ftpState);
2813 return;
cdc33f35 2814 }
62e76326 2815
9e008dda 2816 if ( AI->ai_addrlen != sizeof(struct sockaddr_in) ) {
cc192b50 2817 ipa.FreeAddrInfo(AI);
9e008dda
AJ
2818 /* IPv6 CANNOT send PORT command. */
2819 /* we got here by attempting and failing an EPRT */
2820 /* using the same reply code should simulate a PORT failure */
2821 ftpReadPORT(ftpState);
2822 return;
cc192b50 2823 }
cc192b50 2824
2825 addrptr = (unsigned char *) &((struct sockaddr_in*)AI->ai_addr)->sin_addr;
2826 portptr = (unsigned char *) &((struct sockaddr_in*)AI->ai_addr)->sin_port;
cdc33f35 2827 snprintf(cbuf, 1024, "PORT %d,%d,%d,%d,%d,%d\r\n",
62e76326 2828 addrptr[0], addrptr[1], addrptr[2], addrptr[3],
2829 portptr[0], portptr[1]);
2f47fadf 2830 ftpState->writeCommand(cbuf);
cdc33f35 2831 ftpState->state = SENT_PORT;
cc192b50 2832
2833 ipa.FreeAddrInfo(AI);
3fdadc70 2834}
2835
63be0a78 2836/// \ingroup ServerProtocolFTPInternal
3fdadc70 2837static void
cc192b50 2838ftpReadPORT(FtpStateData * ftpState)
3fdadc70 2839{
cdc33f35 2840 int code = ftpState->ctrl.replycode;
a689bd4e 2841 debugs(9, 3, HERE);
62e76326 2842
cdc33f35 2843 if (code != 200) {
62e76326 2844 /* Fall back on using the same port as the control connection */
bf8fe701 2845 debugs(9, 3, "PORT not supported by remote end");
62e76326 2846 ftpOpenListenSocket(ftpState, 1);
cdc33f35 2847 }
62e76326 2848
cdc33f35 2849 ftpRestOrList(ftpState);
2850}
2851
63be0a78 2852/// \ingroup ServerProtocolFTPInternal
cc192b50 2853static void
2854ftpSendEPRT(FtpStateData * ftpState)
2855{
2856 int fd;
b7ac5457 2857 Ip::Address addr;
cc192b50 2858 struct addrinfo *AI = NULL;
2859 char buf[MAX_IPSTRLEN];
2860
9e008dda 2861 if (Config.Ftp.epsv_all && ftpState->flags.epsv_all_sent) {
a689bd4e 2862 debugs(9, DBG_IMPORTANT, "FTP does not allow EPRT method after 'EPSV ALL' has been sent.");
2863 return;
2864 }
2865
2866 debugs(9, 3, HERE);
cc192b50 2867 ftpState->flags.pasv_supported = 0;
2868 fd = ftpOpenListenSocket(ftpState, 0);
2869
b7ac5457 2870 Ip::Address::InitAddrInfo(AI);
cc192b50 2871
2872 if (getsockname(fd, AI->ai_addr, &AI->ai_addrlen)) {
b7ac5457 2873 Ip::Address::FreeAddrInfo(AI);
a689bd4e 2874 debugs(9, DBG_CRITICAL, HERE << "getsockname(" << fd << ",..): " << xstrerror());
cc192b50 2875
2876 /* XXX Need to set error message */
2877 ftpFail(ftpState);
2878 return;
2879 }
2880
2881 addr = *AI;
2882
2883 /* RFC 2428 defines EPRT as IPv6 equivalent to IPv4 PORT command. */
2884 /* Which can be used by EITHER protocol. */
2885 snprintf(cbuf, 1024, "EPRT |%d|%s|%d|\r\n",
a689bd4e 2886 ( addr.IsIPv6() ? 2 : 1 ),
cc192b50 2887 addr.NtoA(buf,MAX_IPSTRLEN),
2888 addr.GetPort() );
2889
2890 ftpState->writeCommand(cbuf);
2891 ftpState->state = SENT_EPRT;
2892
b7ac5457 2893 Ip::Address::FreeAddrInfo(AI);
cc192b50 2894}
2895
2896static void
2897ftpReadEPRT(FtpStateData * ftpState)
2898{
2899 int code = ftpState->ctrl.replycode;
a689bd4e 2900 debugs(9, 3, HERE);
cc192b50 2901
2902 if (code != 200) {
2903 /* Failover to attempting old PORT command. */
2904 debugs(9, 3, "EPRT not supported by remote end");
2905 ftpSendPORT(ftpState);
2906 return;
2907 }
2908
2909 ftpRestOrList(ftpState);
2910}
2911
a689bd4e 2912/**
63be0a78 2913 \ingroup ServerProtocolFTPInternal
a689bd4e 2914 \par
2915 * "read" handler to accept FTP data connections.
2916 *
63be0a78 2917 \param io comm accept(2) callback parameters
a689bd4e 2918 */
dc56a9b1 2919void FtpStateData::ftpAcceptDataConnection(const CommAcceptCbParams &io)
cdc33f35 2920{
cc192b50 2921 char ntoapeer[MAX_IPSTRLEN];
dc56a9b1 2922 debugs(9, 3, "ftpAcceptDataConnection");
cdc33f35 2923
04f55905
AJ
2924 // one connection accepted. the handler has stopped listening. drop our local pointer to it.
2925 data.listener = NULL;
02d1422b 2926
dc56a9b1 2927 if (EBIT_TEST(entry->flags, ENTRY_ABORTED)) {
2928 abortTransaction("entry aborted when accepting data conn");
62e76326 2929 return;
b044a7eb 2930 }
c4b7a5a9 2931
a689bd4e 2932 /** \par
2933 * When squid.conf ftp_sanitycheck is enabled, check the new connection is actually being
2934 * made by the remote client which is connected to the FTP control socket.
2935 * This prevents third-party hacks, but also third-party load balancing handshakes.
2936 */
00c5afca 2937 if (Config.Ftp.sanitycheck) {
dc56a9b1 2938 io.details.peer.NtoA(ntoapeer,MAX_IPSTRLEN);
2939
2940 if (strcmp(fd_table[ctrl.fd].ipaddr, ntoapeer) != 0) {
9e008dda
AJ
2941 debugs(9, DBG_IMPORTANT,
2942 "FTP data connection from unexpected server (" <<
2943 io.details.peer << "), expecting " <<
2944 fd_table[ctrl.fd].ipaddr);
dc56a9b1 2945
04f55905 2946 /* close the bad soures connection down ASAP. */
dc56a9b1 2947 comm_close(io.nfd);
04f55905
AJ
2948
2949 /* we are ony accepting once, so need to re-open the listener socket. */
9e008dda 2950 typedef CommCbMemFunT<FtpStateData, CommAcceptCbParams> acceptDialer;
4299f876 2951 AsyncCall::Pointer acceptCall = JobCallback(11, 5,
4cb2536f 2952 acceptDialer, this, FtpStateData::ftpAcceptDataConnection);
04f55905 2953 data.listener = new Comm::ListenStateData(data.fd, acceptCall, false);
62e76326 2954 return;
2955 }
00c5afca 2956 }
62e76326 2957
dc56a9b1 2958 if (io.flag != COMM_OK) {
04f55905 2959 debugs(9, DBG_IMPORTANT, "ftpHandleDataAccept: FD " << io.nfd << ": " << xstrerr(io.xerrno));
a689bd4e 2960 /** \todo XXX Need to set error message */
dc56a9b1 2961 ftpFail(this);
62e76326 2962 return;
cdc33f35 2963 }
62e76326 2964
a689bd4e 2965 /**\par
2966 * Replace the Listen socket with the accepted data socket */
94b88585
AR
2967 data.close();
2968 data.opened(io.nfd, dataCloser());
dc56a9b1 2969 data.port = io.details.peer.GetPort();
2970 io.details.peer.NtoA(data.host,SQUIDHOSTNAMELEN);
a689bd4e 2971
dc56a9b1 2972 debugs(9, 3, "ftpAcceptDataConnection: Connected data socket on " <<
9e008dda
AJ
2973 "FD " << io.nfd << " to " << io.details.peer << " FD table says: " <<
2974 "ctrl-peer= " << fd_table[ctrl.fd].ipaddr << ", " <<
2975 "data-peer= " << fd_table[data.fd].ipaddr);
a689bd4e 2976
a689bd4e 2977
dc56a9b1 2978 AsyncCall::Pointer nullCall = NULL;
2979 commSetTimeout(ctrl.fd, -1, nullCall);
62e76326 2980
dc56a9b1 2981 typedef CommCbMemFunT<FtpStateData, CommTimeoutCbParams> TimeoutDialer;
4299f876 2982 AsyncCall::Pointer timeoutCall = JobCallback(9, 5,
4cb2536f 2983 TimeoutDialer, this, FtpStateData::ftpTimeout);
dc56a9b1 2984 commSetTimeout(data.fd, Config.Timeout.read, timeoutCall);
62e76326 2985
a689bd4e 2986 /*\todo XXX We should have a flag to track connect state...
cdc33f35 2987 * host NULL -> not connected, port == local port
2988 * host set -> connected, port == remote port
2989 */
2990 /* Restart state (SENT_NLST/LIST/RETR) */
dc56a9b1 2991 FTP_SM_FUNCS[state] (this);
3fdadc70 2992}
2993
63be0a78 2994/// \ingroup ServerProtocolFTPInternal
3fdadc70 2995static void
2996ftpRestOrList(FtpStateData * ftpState)
2997{
a689bd4e 2998 debugs(9, 3, HERE);
62e76326 2999
94439e4e 3000 if (ftpState->typecode == 'D') {
62e76326 3001 ftpState->flags.isdir = 1;
62e76326 3002
3003 if (ftpState->flags.put) {
3004 ftpSendMkdir(ftpState); /* PUT name;type=d */
3005 } else {
3006 ftpSendNlst(ftpState); /* GET name;type=d sec 3.2.2 of RFC 1738 */
3007 }
94439e4e 3008 } else if (ftpState->flags.put) {
62e76326 3009 ftpSendStor(ftpState);
e55f0142 3010 } else if (ftpState->flags.isdir)
62e76326 3011 ftpSendList(ftpState);
6f0aab86 3012 else if (ftpState->restartable())
62e76326 3013 ftpSendRest(ftpState);
969c39b9 3014 else
62e76326 3015 ftpSendRetr(ftpState);
969c39b9 3016}
3017
63be0a78 3018/// \ingroup ServerProtocolFTPInternal
54220df8 3019static void
3020ftpSendStor(FtpStateData * ftpState)
3021{
a11382aa 3022 /* check the server control channel is still available */
9e008dda 3023 if (!ftpState || !ftpState->haveControlChannel("ftpSendStor"))
a11382aa 3024 return;
3025
a689bd4e 3026 debugs(9, 3, HERE);
3027
9bc73deb 3028 if (ftpState->filepath != NULL) {
62e76326 3029 /* Plain file upload */
3030 snprintf(cbuf, 1024, "STOR %s\r\n", ftpState->filepath);
2f47fadf 3031 ftpState->writeCommand(cbuf);
62e76326 3032 ftpState->state = SENT_STOR;
47f6e231 3033 } else if (ftpState->request->header.getInt64(HDR_CONTENT_LENGTH) > 0) {
62e76326 3034 /* File upload without a filename. use STOU to generate one */
3035 snprintf(cbuf, 1024, "STOU\r\n");
2f47fadf 3036 ftpState->writeCommand(cbuf);
62e76326 3037 ftpState->state = SENT_STOR;
9bc73deb 3038 } else {
62e76326 3039 /* No file to transfer. Only create directories if needed */
3040 ftpSendReply(ftpState);
9bc73deb 3041 }
54220df8 3042}
3043
63be0a78 3044/// \ingroup ServerProtocolFTPInternal
3045/// \deprecated use ftpState->readStor() instead.
54220df8 3046static void
3047ftpReadStor(FtpStateData * ftpState)
3048{
5f8252d2 3049 ftpState->readStor();
3050}
3051
9e008dda
AJ
3052void FtpStateData::readStor()
3053{
5f8252d2 3054 int code = ctrl.replycode;
a689bd4e 3055 debugs(9, 3, HERE);
62e76326 3056
5f8252d2 3057 if (code == 125 || (code == 150 && data.host)) {
123ec4de 3058 if (!startRequestBodyFlow()) { // register to receive body data
5f8252d2 3059 ftpFail(this);
3060 return;
3061 }
3062
a689bd4e 3063 /*\par
3064 * When client status is 125, or 150 without a hostname, Begin data transfer. */
3065 debugs(9, 3, HERE << "starting data transfer");
5f8252d2 3066 sendMoreRequestBody();
a689bd4e 3067 /** \par
62e76326 3068 * Cancel the timeout on the Control socket and
3069 * establish one on the data socket.
3070 */
9e008dda 3071 AsyncCall::Pointer nullCall = NULL;
dc56a9b1 3072 commSetTimeout(ctrl.fd, -1, nullCall);
3073
9e008dda 3074 typedef CommCbMemFunT<FtpStateData, CommTimeoutCbParams> TimeoutDialer;
4299f876 3075 AsyncCall::Pointer timeoutCall = JobCallback(9, 5,
4cb2536f 3076 TimeoutDialer, this, FtpStateData::ftpTimeout);
dc56a9b1 3077
3078 commSetTimeout(data.fd, Config.Timeout.read, timeoutCall);
5f8252d2 3079
3080 state = WRITING_DATA;
a689bd4e 3081 debugs(9, 3, HERE << "writing data channel");
9bc73deb 3082 } else if (code == 150) {
a689bd4e 3083 /*\par
3084 * When client code is 150 with a hostname, Accept data channel. */
dc56a9b1 3085 debugs(9, 3, "ftpReadStor: accepting data channel");
3086 typedef CommCbMemFunT<FtpStateData, CommAcceptCbParams> acceptDialer;
4299f876 3087 AsyncCall::Pointer acceptCall = JobCallback(11, 5,
4cb2536f 3088 acceptDialer, this, FtpStateData::ftpAcceptDataConnection);
dc56a9b1 3089
04f55905 3090 data.listener = new Comm::ListenStateData(data.fd, acceptCall, false);
54220df8 3091 } else {
a689bd4e 3092 debugs(9, DBG_IMPORTANT, HERE << "Unexpected reply code "<< std::setfill('0') << std::setw(3) << code);
5f8252d2 3093 ftpFail(this);
54220df8 3094 }
3095}
3096
63be0a78 3097/// \ingroup ServerProtocolFTPInternal
969c39b9 3098static void
3099ftpSendRest(FtpStateData * ftpState)
3100{
a11382aa 3101 /* check the server control channel is still available */
9e008dda 3102 if (!ftpState || !ftpState->haveControlChannel("ftpSendRest"))
a11382aa 3103 return;
3104
a689bd4e 3105 debugs(9, 3, HERE);
3106
47f6e231 3107 snprintf(cbuf, 1024, "REST %"PRId64"\r\n", ftpState->restart_offset);
2f47fadf 3108 ftpState->writeCommand(cbuf);
969c39b9 3109 ftpState->state = SENT_REST;
3fdadc70 3110}
3111
6f0aab86 3112int
3113FtpStateData::restartable()
cfbf5373 3114{
6f0aab86 3115 if (restart_offset > 0)
62e76326 3116 return 1;
3117
6f0aab86 3118 if (!request->range)
62e76326 3119 return 0;
3120
6f0aab86 3121 if (!flags.binary)
62e76326 3122 return 0;
3123
47f6e231 3124 if (theSize <= 0)
62e76326 3125 return 0;
cfbf5373 3126
47f6e231 3127 int64_t desired_offset = request->range->lowestOffset(theSize);
62e76326 3128
b0e52cb9 3129 if (desired_offset <= 0)
62e76326 3130 return 0;
3131
47f6e231 3132 if (desired_offset >= theSize)
9e008dda 3133 return 0;
b0e52cb9 3134
3135 restart_offset = desired_offset;
cfbf5373 3136 return 1;
3137}
3138
63be0a78 3139/// \ingroup ServerProtocolFTPInternal
3fdadc70 3140static void
3141ftpReadRest(FtpStateData * ftpState)
3142{
3143 int code = ftpState->ctrl.replycode;
a689bd4e 3144 debugs(9, 3, HERE);
3fdadc70 3145 assert(ftpState->restart_offset > 0);
62e76326 3146
3fdadc70 3147 if (code == 350) {
04f7fd38 3148 ftpState->setCurrentOffset(ftpState->restart_offset);
62e76326 3149 ftpSendRetr(ftpState);
3fdadc70 3150 } else if (code > 0) {
a689bd4e 3151 debugs(9, 3, HERE << "REST not supported");
62e76326 3152 ftpState->flags.rest_supported = 0;
3153 ftpSendRetr(ftpState);
3fdadc70 3154 } else {
62e76326 3155 ftpFail(ftpState);
3fdadc70 3156 }
3157}
3158
63be0a78 3159/// \ingroup ServerProtocolFTPInternal
969c39b9 3160static void
3161ftpSendList(FtpStateData * ftpState)
3162{
a11382aa 3163 /* check the server control channel is still available */
9e008dda 3164 if (!ftpState || !ftpState->haveControlChannel("ftpSendList"))
a11382aa 3165 return;
3166
a689bd4e 3167 debugs(9, 3, HERE);
3168
dbfed404 3169 if (ftpState->filepath) {
62e76326 3170 snprintf(cbuf, 1024, "LIST %s\r\n", ftpState->filepath);
dbfed404 3171 } else {
62e76326 3172 snprintf(cbuf, 1024, "LIST\r\n");
dbfed404 3173 }
62e76326 3174
2f47fadf 3175 ftpState->writeCommand(cbuf);
969c39b9 3176 ftpState->state = SENT_LIST;
3177}
3178
63be0a78 3179/// \ingroup ServerProtocolFTPInternal
dbfed404 3180static void
3181ftpSendNlst(FtpStateData * ftpState)
3182{
a11382aa 3183 /* check the server control channel is still available */
9e008dda 3184 if (!ftpState || !ftpState->haveControlChannel("ftpSendNlst"))
a11382aa 3185 return;
3186
a689bd4e 3187 debugs(9, 3, HERE);
3188
e55f0142 3189 ftpState->flags.tried_nlst = 1;
62e76326 3190
dbfed404 3191 if (ftpState->filepath) {
62e76326 3192 snprintf(cbuf, 1024, "NLST %s\r\n", ftpState->filepath);
dbfed404 3193 } else {
62e76326 3194 snprintf(cbuf, 1024, "NLST\r\n");
dbfed404 3195 }
62e76326 3196
2f47fadf 3197 ftpState->writeCommand(cbuf);
dbfed404 3198 ftpState->state = SENT_NLST;
3199}
3200
63be0a78 3201/// \ingroup ServerProtocolFTPInternal
3fdadc70 3202static void
3203ftpReadList(FtpStateData * ftpState)
3204{
3205 int code = ftpState->ctrl.replycode;
a689bd4e 3206 debugs(9, 3, HERE);
62e76326 3207
cdc33f35 3208 if (code == 125 || (code == 150 && ftpState->data.host)) {
62e76326 3209 /* Begin data transfer */
62e76326 3210 /* XXX what about Config.Timeout.read? */
5f8252d2 3211 ftpState->maybeReadVirginBody();
62e76326 3212 ftpState->state = READING_DATA;
3213 /*
3214 * Cancel the timeout on the Control socket and establish one
3215 * on the data socket
3216 */
9e008dda 3217 AsyncCall::Pointer nullCall = NULL;
dc56a9b1 3218 commSetTimeout(ftpState->ctrl.fd, -1, nullCall);
62e76326 3219 return;
cdc33f35 3220 } else if (code == 150) {
62e76326 3221 /* Accept data channel */
9e008dda 3222 typedef CommCbMemFunT<FtpStateData, CommAcceptCbParams> acceptDialer;
4299f876 3223 AsyncCall::Pointer acceptCall = JobCallback(11, 5,
4cb2536f 3224 acceptDialer, ftpState, FtpStateData::ftpAcceptDataConnection);
dc56a9b1 3225
04f55905 3226 ftpState->data.listener = new Comm::ListenStateData(ftpState->data.fd, acceptCall, false);
62e76326 3227 /*
3228 * Cancel the timeout on the Control socket and establish one
3229 * on the data socket
3230 */
9e008dda 3231 AsyncCall::Pointer nullCall = NULL;
dc56a9b1 3232 commSetTimeout(ftpState->ctrl.fd, -1, nullCall);
9e008dda
AJ
3233
3234 typedef CommCbMemFunT<FtpStateData, CommTimeoutCbParams> TimeoutDialer;
4299f876 3235 AsyncCall::Pointer timeoutCall = JobCallback(9, 5,
4cb2536f 3236 TimeoutDialer, ftpState,FtpStateData::ftpTimeout);
dc56a9b1 3237 commSetTimeout(ftpState->data.fd, Config.Timeout.read, timeoutCall);
62e76326 3238 return;
e55f0142 3239 } else if (!ftpState->flags.tried_nlst && code > 300) {
62e76326 3240 ftpSendNlst(ftpState);
3fdadc70 3241 } else {
62e76326 3242 ftpFail(ftpState);
3243 return;
3fdadc70 3244 }
3245}
3246
63be0a78 3247/// \ingroup ServerProtocolFTPInternal
969c39b9 3248static void
3249ftpSendRetr(FtpStateData * ftpState)
3250{
a11382aa 3251 /* check the server control channel is still available */
9e008dda 3252 if (!ftpState || !ftpState->haveControlChannel("ftpSendRetr"))
a11382aa 3253 return;
3254
a689bd4e 3255 debugs(9, 3, HERE);
3256
969c39b9 3257 assert(ftpState->filepath != NULL);
3258 snprintf(cbuf, 1024, "RETR %s\r\n", ftpState->filepath);
2f47fadf 3259 ftpState->writeCommand(cbuf);
969c39b9 3260 ftpState->state = SENT_RETR;
3261}
3262
63be0a78 3263/// \ingroup ServerProtocolFTPInternal
3fdadc70 3264static void
3265ftpReadRetr(FtpStateData * ftpState)
3266{
3267 int code = ftpState->ctrl.replycode;
a689bd4e 3268 debugs(9, 3, HERE);
62e76326 3269
cdc33f35 3270 if (code == 125 || (code == 150 && ftpState->data.host)) {
62e76326 3271 /* Begin data transfer */
a689bd4e 3272 debugs(9, 3, HERE << "reading data channel");
62e76326 3273 /* XXX what about Config.Timeout.read? */
5f8252d2 3274 ftpState->maybeReadVirginBody();
62e76326 3275 ftpState->state = READING_DATA;
3276 /*
3277 * Cancel the timeout on the Control socket and establish one
3278 * on the data socket
3279 */
9e008dda 3280 AsyncCall::Pointer nullCall = NULL;
dc56a9b1 3281 commSetTimeout(ftpState->ctrl.fd, -1, nullCall);
cdc33f35 3282 } else if (code == 150) {
62e76326 3283 /* Accept data channel */
9e008dda 3284 typedef CommCbMemFunT<FtpStateData, CommAcceptCbParams> acceptDialer;
4299f876 3285 AsyncCall::Pointer acceptCall = JobCallback(11, 5,
4cb2536f 3286 acceptDialer, ftpState, FtpStateData::ftpAcceptDataConnection);
04f55905 3287 ftpState->data.listener = new Comm::ListenStateData(ftpState->data.fd, acceptCall, false);
62e76326 3288 /*
3289 * Cancel the timeout on the Control socket and establish one
3290 * on the data socket
3291 */
9e008dda 3292 AsyncCall::Pointer nullCall = NULL;
dc56a9b1 3293 commSetTimeout(ftpState->ctrl.fd, -1, nullCall);
3294
9e008dda 3295 typedef CommCbMemFunT<FtpStateData, CommTimeoutCbParams> TimeoutDialer;
4299f876 3296 AsyncCall::Pointer timeoutCall = JobCallback(9, 5,
4cb2536f 3297 TimeoutDialer, ftpState,FtpStateData::ftpTimeout);
dc56a9b1 3298 commSetTimeout(ftpState->data.fd, Config.Timeout.read, timeoutCall);
cdc33f35 3299 } else if (code >= 300) {
62e76326 3300 if (!ftpState->flags.try_slash_hack) {
3301 /* Try this as a directory missing trailing slash... */
6f0aab86 3302 ftpState->hackShortcut(ftpSendCwd);
62e76326 3303 } else {
3304 ftpFail(ftpState);
3305 }
cdc33f35 3306 } else {
62e76326 3307 ftpFail(ftpState);
3fdadc70 3308 }
3309}
3310
0477a072
AJ
3311/**
3312 * Generate the HTTP headers and template fluff around an FTP
3313 * directory listing display.
3314 */
3315void
3316FtpStateData::completedListing()
3317{
3318 assert(entry);
3319 entry->lock();
3320 ErrorState *ferr = errorCon(ERR_DIR_LISTING, HTTP_OK, request);
3321 ferr->ftp.listing = &listing;
f0324b7f 3322 ferr->ftp.cwd_msg = xstrdup(cwd_message.size()? cwd_message.termedBuf() : "");
0477a072
AJ
3323 ferr->ftp.server_msg = ctrl.message;
3324 ctrl.message = NULL;
3325 entry->replaceHttpReply( ferr->BuildHttpReply() );
3326 errorStateFree(ferr);
3327 EBIT_CLR(entry->flags, ENTRY_FWD_HDR_WAIT);
3328 entry->flush();
3329 entry->unlock();
3330}
3331
3332
63be0a78 3333/// \ingroup ServerProtocolFTPInternal
3fdadc70 3334static void
3335ftpReadTransferDone(FtpStateData * ftpState)
3336{
3337 int code = ftpState->ctrl.replycode;
a689bd4e 3338 debugs(9, 3, HERE);
62e76326 3339
bbf07a94 3340 if (code == 226 || code == 250) {
62e76326 3341 /* Connection closed; retrieval done. */
0477a072
AJ
3342 if (ftpState->flags.listing) {
3343 ftpState->completedListing();
3344 /* QUIT operation handles sending the reply to client */
3345 }
c0d75e74 3346 ftpSendQuit(ftpState);
9bc73deb 3347 } else { /* != 226 */
a689bd4e 3348 debugs(9, DBG_IMPORTANT, HERE << "Got code " << code << " after reading data");
8043ea13 3349 ftpState->failed(ERR_FTP_FAILURE, 0);
6f0aab86 3350 /* failed closes ctrl.fd and frees ftpState */
62e76326 3351 return;
3fdadc70 3352 }
3353}
3354
5f8252d2 3355// premature end of the request body
6f0aab86 3356void
5f8252d2 3357FtpStateData::handleRequestBodyProducerAborted()
3fdadc70 3358{
5f8252d2 3359 ServerStateData::handleRequestBodyProducerAborted();
a689bd4e 3360 debugs(9, 3, HERE << "ftpState=" << this);
5f8252d2 3361 failed(ERR_READ_ERROR, 0);
94439e4e 3362}
3363
63be0a78 3364/**
3365 * This will be called when the put write is completed
3366 */
6f0aab86 3367void
dc56a9b1 3368FtpStateData::sentRequestBody(const CommIoCbParams &io)
94439e4e 3369{
dc56a9b1 3370 if (io.size > 0)
3371 kb_incr(&statCounter.server.ftp.kbytes_out, io.size);
3372 ServerStateData::sentRequestBody(io);
94439e4e 3373}
3374
63be0a78 3375/// \ingroup ServerProtocolFTPInternal
94439e4e 3376static void
3377ftpWriteTransferDone(FtpStateData * ftpState)
3378{
3379 int code = ftpState->ctrl.replycode;
a689bd4e 3380 debugs(9, 3, HERE);
62e76326 3381
bbf07a94 3382 if (!(code == 226 || code == 250)) {
a689bd4e 3383 debugs(9, DBG_IMPORTANT, HERE << "Got code " << code << " after sending data");
8043ea13 3384 ftpState->failed(ERR_FTP_PUT_ERROR, 0);
62e76326 3385 return;
94439e4e 3386 }
62e76326 3387
3900307b 3388 ftpState->entry->timestampsSet(); /* XXX Is this needed? */
94439e4e 3389 ftpSendReply(ftpState);
969c39b9 3390}
3391
63be0a78 3392/// \ingroup ServerProtocolFTPInternal
969c39b9 3393static void
3394ftpSendQuit(FtpStateData * ftpState)
3395{
a11382aa 3396 /* check the server control channel is still available */
9e008dda 3397 if (!ftpState || !ftpState->haveControlChannel("ftpSendQuit"))
a11382aa 3398 return;
3399
56878878 3400 snprintf(cbuf, 1024, "QUIT\r\n");
2f47fadf 3401 ftpState->writeCommand(cbuf);
3fdadc70 3402 ftpState->state = SENT_QUIT;
3403}
3404
0477a072
AJ
3405/**
3406 * \ingroup ServerProtocolFTPInternal
3407 *
3408 * This completes a client FTP operation with success or other page
3409 * generated and stored in the entry field by the code issuing QUIT.
3410 */
3fdadc70 3411static void
3412ftpReadQuit(FtpStateData * ftpState)
3413{
5f8252d2 3414 ftpState->serverComplete();
3fdadc70 3415}
3416
63be0a78 3417/// \ingroup ServerProtocolFTPInternal
969c39b9 3418static void
3419ftpTrySlashHack(FtpStateData * ftpState)
3420{
3421 char *path;
e55f0142 3422 ftpState->flags.try_slash_hack = 1;
969c39b9 3423 /* Free old paths */
62e76326 3424
a689bd4e 3425 debugs(9, 3, HERE);
3426
969c39b9 3427 if (ftpState->pathcomps)
62e76326 3428 wordlistDestroy(&ftpState->pathcomps);
3429
969c39b9 3430 safe_free(ftpState->filepath);
62e76326 3431
969c39b9 3432 /* Build the new path (urlpath begins with /) */
d53b3f6d 3433 path = xstrdup(ftpState->request->urlpath.termedBuf());
62e76326 3434
969c39b9 3435 rfc1738_unescape(path);
62e76326 3436
969c39b9 3437 ftpState->filepath = path;
62e76326 3438
969c39b9 3439 /* And off we go */
dbfed404 3440 ftpGetFile(ftpState);
969c39b9 3441}
3442
63be0a78 3443/**
3444 * Forget hack status. Next error is shown to the user
3445 */
6f0aab86 3446void
3447FtpStateData::unhack()
0f169992 3448{
a689bd4e 3449 debugs(9, 3, HERE);
3450
6f0aab86 3451 if (old_request != NULL) {
3452 safe_free(old_request);
3453 safe_free(old_reply);
0f169992 3454 }
3455}
3456
6f0aab86 3457void
3458FtpStateData::hackShortcut(FTPSM * nextState)
969c39b9 3459{
c7e0305b 3460 /* Clear some unwanted state */
9fa2e208 3461 setCurrentOffset(0);
6f0aab86 3462 restart_offset = 0;
0f169992 3463 /* Save old error message & some state info */
62e76326 3464
a689bd4e 3465 debugs(9, 3, HERE);
3466
6f0aab86 3467 if (old_request == NULL) {
3468 old_request = ctrl.last_command;
3469 ctrl.last_command = NULL;
3470 old_reply = ctrl.last_reply;
3471 ctrl.last_reply = NULL;
62e76326 3472
6f0aab86 3473 if (pathcomps == NULL && filepath != NULL)
3474 old_filepath = xstrdup(filepath);
0f169992 3475 }
62e76326 3476
969c39b9 3477 /* Jump to the "hack" state */
6f0aab86 3478 nextState(this);
969c39b9 3479}
3480
63be0a78 3481/// \ingroup ServerProtocolFTPInternal
3fdadc70 3482static void
6f0aab86 3483ftpFail(FtpStateData *ftpState)
3fdadc70 3484{
4d6c56a6 3485 debugs(9, 6, HERE << "flags(" <<
9e008dda
AJ
3486 (ftpState->flags.isdir?"IS_DIR,":"") <<
3487 (ftpState->flags.try_slash_hack?"TRY_SLASH_HACK":"") << "), " <<
3488 "mdtm=" << ftpState->mdtm << ", size=" << ftpState->theSize <<
3489 "slashhack=" << (ftpState->request->urlpath.caseCmp("/%2f", 4)==0? "T":"F") );
62e76326 3490
a689bd4e 3491 /* Try the / hack to support "Netscape" FTP URL's for retreiving files */
0cdcddb9 3492 if (!ftpState->flags.isdir && /* Not a directory */
62e76326 3493 !ftpState->flags.try_slash_hack && /* Not in slash hack */
47f6e231 3494 ftpState->mdtm <= 0 && ftpState->theSize < 0 && /* Not known as a file */
30abd221 3495 ftpState->request->urlpath.caseCmp("/%2f", 4) != 0) { /* No slash encoded */
62e76326 3496
3497 switch (ftpState->state) {
3498
3499 case SENT_CWD:
3500
3501 case SENT_RETR:
3502 /* Try the / hack */
6f0aab86 3503 ftpState->hackShortcut(ftpTrySlashHack);
62e76326 3504 return;
3505
3506 default:
3507 break;
3508 }
969c39b9 3509 }
62e76326 3510
6f0aab86 3511 ftpState->failed(ERR_NONE, 0);
3512 /* failed() closes ctrl.fd and frees this */
9bc73deb 3513}
3514
6f0aab86 3515void
3516FtpStateData::failed(err_type error, int xerrno)
9bc73deb 3517{
4d6c56a6 3518 debugs(9,3,HERE << "entry-null=" << (entry?entry->isEmpty():0) << ", entry=" << entry);
528b2c61 3519 if (entry->isEmpty())
6f0aab86 3520 failedErrorMessage(error, xerrno);
62e76326 3521
5f8252d2 3522 serverComplete();
9bc73deb 3523}
3524
6f0aab86 3525void
3526FtpStateData::failedErrorMessage(err_type error, int xerrno)
9bc73deb 3527{
c3669e32 3528 ErrorState *ftperr;
a2c963ae 3529 const char *command, *reply;
c3669e32 3530
b6a2f15e 3531 /* Translate FTP errors into HTTP errors */
c3669e32 3532 ftperr = NULL;
62e76326 3533
9bc73deb 3534 switch (error) {
62e76326 3535
9bc73deb 3536 case ERR_NONE:
62e76326 3537
6f0aab86 3538 switch (state) {
62e76326 3539
3540 case SENT_USER:
3541
3542 case SENT_PASS:
3543
6f0aab86 3544 if (ctrl.replycode > 500)
3545 if (password_url)
c3669e32 3546 ftperr = errorCon(ERR_FTP_FORBIDDEN, HTTP_FORBIDDEN, fwd->request);
c4a2d5e1 3547 else
c3669e32 3548 ftperr = errorCon(ERR_FTP_FORBIDDEN, HTTP_UNAUTHORIZED, fwd->request);
c4a2d5e1 3549
6f0aab86 3550 else if (ctrl.replycode == 421)
c3669e32 3551 ftperr = errorCon(ERR_FTP_UNAVAILABLE, HTTP_SERVICE_UNAVAILABLE, fwd->request);
62e76326 3552
3553 break;
3554
3555 case SENT_CWD:
3556
3557 case SENT_RETR:
6f0aab86 3558 if (ctrl.replycode == 550)
c3669e32 3559 ftperr = errorCon(ERR_FTP_NOT_FOUND, HTTP_NOT_FOUND, fwd->request);
62e76326 3560
3561 break;
3562
3563 default:
3564 break;
3565 }
3566
3567 break;
3568
9bc73deb 3569 case ERR_READ_TIMEOUT:
c3669e32 3570 ftperr = errorCon(error, HTTP_GATEWAY_TIMEOUT, fwd->request);
62e76326 3571 break;
3572
b6a2f15e 3573 default:
c3669e32 3574 ftperr = errorCon(error, HTTP_BAD_GATEWAY, fwd->request);
62e76326 3575 break;
b6a2f15e 3576 }
62e76326 3577
c3669e32
AJ
3578 if (ftperr == NULL)
3579 ftperr = errorCon(ERR_FTP_FAILURE, HTTP_BAD_GATEWAY, fwd->request);
62e76326 3580
c3669e32 3581 ftperr->xerrno = xerrno;
62e76326 3582
c3669e32 3583 ftperr->ftp.server_msg = ctrl.message;
6f0aab86 3584 ctrl.message = NULL;
62e76326 3585
6f0aab86 3586 if (old_request)
3587 command = old_request;
969c39b9 3588 else
6f0aab86 3589 command = ctrl.last_command;
62e76326 3590
9bc73deb 3591 if (command && strncmp(command, "PASS", 4) == 0)
62e76326 3592 command = "PASS <yourpassword>";
3593
6f0aab86 3594 if (old_reply)
3595 reply = old_reply;
969c39b9 3596 else
6f0aab86 3597 reply = ctrl.last_reply;
62e76326 3598
9bc73deb 3599 if (command)
c3669e32 3600 ftperr->ftp.request = xstrdup(command);
62e76326 3601
9bc73deb 3602 if (reply)
c3669e32 3603 ftperr->ftp.reply = xstrdup(reply);
62e76326 3604
c3669e32
AJ
3605 entry->replaceHttpReply( ftperr->BuildHttpReply() );
3606 errorStateFree(ftperr);
3fdadc70 3607}
3608
63be0a78 3609/// \ingroup ServerProtocolFTPInternal
54220df8 3610static void
3611ftpSendReply(FtpStateData * ftpState)
3612{
3613 ErrorState *err;
acce49bf 3614 int code = ftpState->ctrl.replycode;
728da2ee 3615 http_status http_code;
3616 err_type err_code = ERR_NONE;
a11382aa 3617
a689bd4e 3618 debugs(9, 3, HERE << ftpState->entry->url() << ", code " << code);
62e76326 3619
fa80a8ef 3620 if (cbdataReferenceValid(ftpState))
a689bd4e 3621 debugs(9, 5, HERE << "ftpState (" << ftpState << ") is valid!");
62e76326 3622
bbf07a94 3623 if (code == 226 || code == 250) {
62e76326 3624 err_code = (ftpState->mdtm > 0) ? ERR_FTP_PUT_MODIFIED : ERR_FTP_PUT_CREATED;
3625 http_code = (ftpState->mdtm > 0) ? HTTP_ACCEPTED : HTTP_CREATED;
9bc73deb 3626 } else if (code == 227) {
62e76326 3627 err_code = ERR_FTP_PUT_CREATED;
3628 http_code = HTTP_CREATED;
54220df8 3629 } else {
62e76326 3630 err_code = ERR_FTP_PUT_ERROR;
3631 http_code = HTTP_INTERNAL_SERVER_ERROR;
54220df8 3632 }
62e76326 3633
2cc81f1f 3634 err = errorCon(err_code, http_code, ftpState->request);
62e76326 3635
54220df8 3636 if (ftpState->old_request)
62e76326 3637 err->ftp.request = xstrdup(ftpState->old_request);
54220df8 3638 else
62e76326 3639 err->ftp.request = xstrdup(ftpState->ctrl.last_command);
3640
54220df8 3641 if (ftpState->old_reply)
62e76326 3642 err->ftp.reply = xstrdup(ftpState->old_reply);
0dfb298f 3643 else if (ftpState->ctrl.last_reply)
62e76326 3644 err->ftp.reply = xstrdup(ftpState->ctrl.last_reply);
0dfb298f 3645 else
62e76326 3646 err->ftp.reply = xstrdup("");
3647
c3669e32
AJ
3648 ftpState->entry->replaceHttpReply( err->BuildHttpReply() );
3649 errorStateFree(err);
62e76326 3650
9bc73deb 3651 ftpSendQuit(ftpState);
54220df8 3652}
3653
6f0aab86 3654void
3655FtpStateData::appendSuccessHeader()
3fdadc70 3656{
a2c963ae 3657 const char *mime_type = NULL;
3658 const char *mime_enc = NULL;
30abd221 3659 String urlpath = request->urlpath;
02922e76 3660 const char *filename = NULL;
3661 const char *t = NULL;
253caccb 3662
a689bd4e 3663 debugs(9, 3, HERE);
ee65546f 3664
6f0aab86 3665 if (flags.http_header_sent)
62e76326 3666 return;
3667
585ab260 3668 HttpReply *reply = new HttpReply;
3669
6f0aab86 3670 flags.http_header_sent = 1;
62e76326 3671
0477a072 3672 assert(entry->isEmpty());
62e76326 3673
0477a072 3674 EBIT_CLR(entry->flags, ENTRY_FWD_HDR_WAIT);
62e76326 3675
0477a072 3676 entry->buffer(); /* released when done processing current data payload */
b66315e4 3677
d53b3f6d 3678 filename = (t = urlpath.rpos('/')) ? t + 1 : urlpath.termedBuf();
62e76326 3679
6f0aab86 3680 if (flags.isdir) {
62e76326 3681 mime_type = "text/html";
3fdadc70 3682 } else {
6f0aab86 3683 switch (typecode) {
62e76326 3684
3685 case 'I':
3686 mime_type = "application/octet-stream";
3687 mime_enc = mimeGetContentEncoding(filename);
3688 break;
3689
3690 case 'A':
3691 mime_type = "text/plain";
3692 break;
3693
3694 default:
3695 mime_type = mimeGetContentType(filename);
3696 mime_enc = mimeGetContentEncoding(filename);
3697 break;
3698 }
3fdadc70 3699 }
62e76326 3700
cb69b4c7 3701 /* set standard stuff */
62e76326 3702
9fa2e208 3703 if (0 == getCurrentOffset()) {
690b22c6 3704 /* Full reply */
11992b6f 3705 reply->setHeaders(HTTP_OK, "Gatewaying", mime_type, theSize, mdtm, -2);
9fa2e208 3706 } else if (theSize < getCurrentOffset()) {
9e008dda
AJ
3707 /*
3708 * DPW 2007-05-04
3709 * offset should not be larger than theSize. We should
3710 * not be seeing this condition any more because we'll only
3711 * send REST if we know the theSize and if it is less than theSize.
3712 */
3713 debugs(0,DBG_CRITICAL,HERE << "Whoops! " <<
9fa2e208 3714 " current offset=" << getCurrentOffset() <<
9e008dda
AJ
3715 ", but theSize=" << theSize <<
3716 ". assuming full content response");
11992b6f 3717 reply->setHeaders(HTTP_OK, "Gatewaying", mime_type, theSize, mdtm, -2);
690b22c6 3718 } else {
62e76326 3719 /* Partial reply */
3720 HttpHdrRangeSpec range_spec;
9fa2e208
CT
3721 range_spec.offset = getCurrentOffset();
3722 range_spec.length = theSize - getCurrentOffset();
11992b6f 3723 reply->setHeaders(HTTP_PARTIAL_CONTENT, "Gatewaying", mime_type, theSize - getCurrentOffset(), mdtm, -2);
47f6e231 3724 httpHeaderAddContRange(&reply->header, range_spec, theSize);
cfbf5373 3725 }
62e76326 3726
cb69b4c7 3727 /* additional info */
3728 if (mime_enc)
a9925b40 3729 reply->header.putStr(HDR_CONTENT_ENCODING, mime_enc);
62e76326 3730
585ab260 3731 setVirginReply(reply);
3732 adaptOrFinalizeReply();
063a47b5 3733}
3734
3735void
3736FtpStateData::haveParsedReplyHeaders()
3737{
c1520b67
AJ
3738 ServerStateData::haveParsedReplyHeaders();
3739
063a47b5 3740 StoreEntry *e = entry;
62e76326 3741
3900307b 3742 e->timestampsSet();
62e76326 3743
6f0aab86 3744 if (flags.authenticated) {
62e76326 3745 /*
3746 * Authenticated requests can't be cached.
3747 */
5f33b71d 3748 e->release();
9fa2e208 3749 } else if (EBIT_TEST(e->flags, ENTRY_CACHABLE) && !getCurrentOffset()) {
d88e3c49 3750 e->setPublicKey();
c68e9c6b 3751 } else {
5f33b71d 3752 e->release();
c68e9c6b 3753 }
77a30ebb 3754}
bfcaf585 3755
6f0aab86 3756HttpReply *
3757FtpStateData::ftpAuthRequired(HttpRequest * request, const char *realm)
cb69b4c7 3758{
2cc81f1f 3759 ErrorState *err = errorCon(ERR_CACHE_ACCESS_DENIED, HTTP_UNAUTHORIZED, request);
c70281f8 3760 HttpReply *newrep = err->BuildHttpReply();
cb69b4c7 3761 errorStateFree(err);
63259c34 3762 /* add Authenticate header */
a9925b40 3763 newrep->header.putAuth("Basic", realm);
4a56ee8d 3764 return newrep;
cb69b4c7 3765}
8f872bb6 3766
cc192b50 3767/**
63be0a78 3768 \ingroup ServerProtocolFTPAPI
3769 \todo Should be a URL class API call.
3770 *
cc192b50 3771 * Construct an URI with leading / in PATH portion for use by CWD command
3772 * possibly others. FTP encodes absolute paths as beginning with '/'
3773 * after the initial URI path delimiter, which happens to be / itself.
3774 * This makes FTP absolute URI appear as: ftp:host:port//root/path
3775 * To encompass older software which compacts multiple // to / in transit
9e008dda 3776 * We use standard URI-encoding on the second / making it
cc192b50 3777 * ftp:host:port/%2froot/path AKA 'the FTP %2f hack'.
3778 */
3779const char *
3780ftpUrlWith2f(HttpRequest * request)
8f872bb6 3781{
cc192b50 3782 String newbuf = "%2f";
62e76326 3783
23d92c64 3784 if (request->protocol != PROTO_FTP)
62e76326 3785 return NULL;
3786
d53b3f6d 3787 if ( request->urlpath[0]=='/' ) {
cc192b50 3788 newbuf.append(request->urlpath);
3789 request->urlpath.absorb(newbuf);
3790 safe_free(request->canonical);
d53b3f6d 3791 } else if ( !strncmp(request->urlpath.termedBuf(), "%2f", 3) ) {
826a1fed 3792 newbuf.append(request->urlpath.substr(1,request->urlpath.size()));
cc192b50 3793 request->urlpath.absorb(newbuf);
3794 safe_free(request->canonical);
8f872bb6 3795 }
62e76326 3796
cc192b50 3797 return urlCanonical(request);
8f872bb6 3798}
253caccb 3799
3800void
3801FtpStateData::printfReplyBody(const char *fmt, ...)
3802{
3803 va_list args;
3804 va_start (args, fmt);
3805 static char buf[4096];
3806 buf[0] = '\0';
3807 vsnprintf(buf, 4096, fmt, args);
3808 writeReplyBody(buf, strlen(buf));
3809}
3810
63be0a78 3811/**
253caccb 3812 * Call this when there is data from the origin server
3813 * which should be sent to either StoreEntry, or to ICAP...
3814 */
3815void
e053c141 3816FtpStateData::writeReplyBody(const char *dataToWrite, size_t dataLength)
253caccb 3817{
e053c141
FC
3818 debugs(9, 5, HERE << "writing " << dataLength << " bytes to the reply");
3819 addVirginReplyBody(dataToWrite, dataLength);
253caccb 3820}
3821
63be0a78 3822/**
3823 * called after we wrote the last byte of the request body
3824 */
16846d00 3825void
5f8252d2 3826FtpStateData::doneSendingRequestBody()
16846d00 3827{
39cb8c41 3828 ServerStateData::doneSendingRequestBody();
a689bd4e 3829 debugs(9,3, HERE);
f9ac241b 3830 dataComplete();
9e008dda
AJ
3831 /* NP: RFC 959 3.3. DATA CONNECTION MANAGEMENT
3832 * if transfer type is 'stream' call dataComplete()
3833 * otherwise leave open. (reschedule control channel read?)
3834 */
16846d00 3835}
3836
63be0a78 3837/**
3838 * A hack to ensure we do not double-complete on the forward entry.
3839 *
9e008dda 3840 \todo FtpStateData logic should probably be rewritten to avoid
63be0a78 3841 * double-completion or FwdState should be rewritten to allow it.
3842 */
16846d00 3843void
5f8252d2 3844FtpStateData::completeForwarding()
16846d00 3845{
5f8252d2 3846 if (fwd == NULL || flags.completed_forwarding) {
a689bd4e 3847 debugs(9, 3, HERE << "completeForwarding avoids " <<
9e008dda
AJ
3848 "double-complete on FD " << ctrl.fd << ", Data FD " << data.fd <<
3849 ", this " << this << ", fwd " << fwd);
5f8252d2 3850 return;
3851 }
16846d00 3852
5f8252d2 3853 flags.completed_forwarding = true;
3854 ServerStateData::completeForwarding();
16846d00 3855}
3856
63be0a78 3857/**
3858 * Close the FTP server connection(s). Used by serverComplete().
3859 */
253caccb 3860void
5f8252d2 3861FtpStateData::closeServer()
253caccb 3862{
a689bd4e 3863 debugs(9,3, HERE << "closing FTP server FD " << ctrl.fd << ", Data FD " << data.fd << ", this " << this);
253caccb 3864
c0d75e74 3865 if (ctrl.fd > -1) {
3866 fwd->unregister(ctrl.fd);
94b88585 3867 ctrl.close();
c0d75e74 3868 }
253caccb 3869
94b88585 3870 data.close();
5f8252d2 3871}
253caccb 3872
63be0a78 3873/**
3874 * Did we close all FTP server connection(s)?
3875 *
04f55905 3876 \retval true Both server control and data channels are closed. And not waitigng for a new data connection to open.
63be0a78 3877 \retval false Either control channel or data is still active.
3878 */
5f8252d2 3879bool
3880FtpStateData::doneWithServer() const
3881{
3882 return ctrl.fd < 0 && data.fd < 0;
3883}
c0d75e74 3884
63be0a78 3885/**
3886 * Have we lost the FTP server control channel?
3887 *
3888 \retval true The server control channel is available.
3889 \retval false The server control channel is not available.
3890 */
a11382aa 3891bool
4784c403 3892FtpStateData::haveControlChannel(const char *caller_name) const
a11382aa 3893{
9e008dda 3894 if (doneWithServer())
33ed5343 3895 return false;
a11382aa 3896
33ed5343 3897 /* doneWithServer() only checks BOTH channels are closed. */
9e008dda 3898 if (ctrl.fd < 0) {
a689bd4e 3899 debugs(9, DBG_IMPORTANT, "WARNING! FTP Server Control channel is closed, but Data channel still active.");
33ed5343 3900 debugs(9, 2, caller_name << ": attempted on a closed FTP channel.");
3901 return false;
3902 }
3903
3904 return true;
a11382aa 3905}
3906
63be0a78 3907/**
3908 * Quickly abort the transaction
3909 *
3910 \todo destruction should be sufficient as the destructor should cleanup,
3911 * including canceling close handlers
3912 */
5f8252d2 3913void
3914FtpStateData::abortTransaction(const char *reason)
3915{
a689bd4e 3916 debugs(9, 3, HERE << "aborting transaction for " << reason <<
9e008dda 3917 "; FD " << ctrl.fd << ", Data FD " << data.fd << ", this " << this);
3e8c047e 3918 if (ctrl.fd >= 0) {
5f8252d2 3919 comm_close(ctrl.fd);
3e8c047e 3920 return;
3921 }
5a5b4ce1 3922
3e8c047e 3923 fwd->handleUnregisteredServerEnd();
dc56a9b1 3924 deleteThis("FtpStateData::abortTransaction");
253caccb 3925}
94b88585
AR
3926
3927/// creates a data channel Comm close callback
3928AsyncCall::Pointer
3929FtpStateData::dataCloser()
3930{
3931 typedef CommCbMemFunT<FtpStateData, CommCloseCbParams> Dialer;
4299f876 3932 return JobCallback(9, 5, Dialer, this, FtpStateData::dataClosed);
94b88585
AR
3933}
3934
3935/// configures the channel with a descriptor and registers a close handler
3936void
3937FtpChannel::opened(int aFd, const AsyncCall::Pointer &aCloser)
3938{
3939 assert(fd < 0);
3940 assert(closer == NULL);
3941
3942 assert(aFd >= 0);
3943 assert(aCloser != NULL);
3944
3945 fd = aFd;
3946 closer = aCloser;
3947 comm_add_close_handler(fd, closer);
3948}
3949
3950/// planned close: removes the close handler and calls comm_close
3951void
3952FtpChannel::close()
3953{
04f55905
AJ
3954 // channels with active listeners will be closed when the listener handler dies.
3955 if (listener) {
3956 delete listener;
3957 listener = NULL;
3958 comm_remove_close_handler(fd, closer);
3959 closer = NULL;
3960 fd = -1;
97b8ac39 3961 } else if (fd >= 0) {
94b88585
AR
3962 comm_remove_close_handler(fd, closer);
3963 closer = NULL;
3964 comm_close(fd); // we do not expect to be called back
3965 fd = -1;
3966 }
3967}
3968
3969/// just resets fd and close handler
3970void
3971FtpChannel::clear()
3972{
3973 fd = -1;
3974 closer = NULL;
3975}