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