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