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