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