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