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