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