]> git.ipfire.org Git - thirdparty/squid.git/blame - src/ftp.cc
Bug 3433: Segfault closing SNMP
[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
6b679a01 2470 ftpState->data.host = xstrdup(fd_table[ftpState->ctrl.conn->fd].ipaddr);
a689bd4e 2471
2472 safe_free(ftpState->ctrl.last_command);
2473
2474 safe_free(ftpState->ctrl.last_reply);
2475
2476 ftpState->ctrl.last_command = xstrdup("Connect to server data port");
2477
6b679a01 2478 // Generate a new data channel descriptor to be opened.
f9b72e0c 2479 Comm::ConnectionPointer conn = new Comm::Connection;
6b679a01 2480 conn->local = ftpState->ctrl.conn->local;
6200d3d6 2481 conn->local.SetPort(0);
6b679a01 2482 conn->remote = ftpState->ctrl.conn->remote;
cfd66529 2483 conn->remote.SetPort(port);
6b679a01
AJ
2484
2485 debugs(9, 3, HERE << "connecting to " << conn->remote);
cfd66529 2486
e3a4aecc
AJ
2487 ftpState->data.opener = commCbCall(9,3, "FtpStateData::ftpPasvCallback", CommConnectCbPtrFun(FtpStateData::ftpPasvCallback, ftpState));
2488 Comm::ConnOpener *cs = new Comm::ConnOpener(conn, ftpState->data.opener, Config.Timeout.connect);
aed188fd 2489 cs->setHost(ftpState->data.host);
855150a4 2490 AsyncJob::Start(cs);
a689bd4e 2491}
cc192b50 2492
63be0a78 2493/** \ingroup ServerProtocolFTPInternal
2494 *
a689bd4e 2495 * Send Passive connection request.
2496 * Default method is to use modern EPSV request.
2497 * The failover mechanism should check for previous state and re-call with alternates on failure.
2498 */
3fdadc70 2499static void
a689bd4e 2500ftpSendPassive(FtpStateData * ftpState)
3fdadc70 2501{
a689bd4e 2502 /** Checks the server control channel is still available before running. */
9e008dda 2503 if (!ftpState || !ftpState->haveControlChannel("ftpSendPassive"))
a11382aa 2504 return;
2505
a689bd4e 2506 debugs(9, 3, HERE);
2507
2508 /** \par
2509 * Checks for EPSV ALL special conditions:
2510 * If enabled to be sent, squid MUST NOT request any other connect methods.
2511 * If 'ALL' is sent and fails the entire FTP Session fails.
2512 * NP: By my reading exact EPSV protocols maybe attempted, but only EPSV method. */
9e008dda 2513 if (Config.Ftp.epsv_all && ftpState->flags.epsv_all_sent && ftpState->state == SENT_EPSV_1 ) {
a689bd4e 2514 debugs(9, DBG_IMPORTANT, "FTP does not allow PASV method after 'EPSV ALL' has been sent.");
2515 ftpFail(ftpState);
2516 return;
2517 }
ee65546f 2518
a689bd4e 2519 /** \par
2520 * Checks for 'HEAD' method request and passes off for special handling by FtpStateData::processHeadResponse(). */
47f6e231 2521 if (ftpState->request->method == METHOD_HEAD && (ftpState->flags.isdir || ftpState->theSize != -1)) {
063a47b5 2522 ftpState->processHeadResponse(); // may call serverComplete
62e76326 2523 return;
7497ceef 2524 }
62e76326 2525
94b88585
AR
2526 /// Closes any old FTP-Data connection which may exist. */
2527 ftpState->data.close();
62e76326 2528
a689bd4e 2529 /** \par
2530 * Checks for previous EPSV/PASV failures on this server/session.
2531 * Diverts to EPRT immediately if they are not working. */
e55f0142 2532 if (!ftpState->flags.pasv_supported) {
cc192b50 2533 ftpSendEPRT(ftpState);
62e76326 2534 return;
30a4f2a8 2535 }
62e76326 2536
a689bd4e 2537 /** \par
2538 * Send EPSV (ALL,2,1) or PASV on the control channel.
2539 *
d85b8894 2540 * - EPSV ALL is used if enabled.
9adb5bc5
AJ
2541 * - EPSV 2 is used if ALL is disabled and IPv6 is available and ctrl channel is IPv6.
2542 * - EPSV 1 is used if EPSV 2 (IPv6) fails or is not available or ctrl channel is IPv4.
d85b8894 2543 * - PASV is used if EPSV 1 fails.
a689bd4e 2544 */
9e008dda 2545 switch (ftpState->state) {
9adb5bc5
AJ
2546 case SENT_EPSV_ALL: /* EPSV ALL resulted in a bad response. Try ther EPSV methods. */
2547 ftpState->flags.epsv_all_sent = true;
6b679a01
AJ
2548 if (ftpState->ctrl.conn->local.IsIPv6()) {
2549 debugs(9, 5, HERE << "FTP Channel is IPv6 (" << ftpState->ctrl.conn->remote << ") attempting EPSV 2 after EPSV ALL has failed.");
903198a7 2550 snprintf(cbuf, CTRL_BUFLEN, "EPSV 2\r\n");
9adb5bc5
AJ
2551 ftpState->state = SENT_EPSV_2;
2552 break;
2553 }
2554 // else fall through to skip EPSV 2
62e76326 2555
a689bd4e 2556 case SENT_EPSV_2: /* EPSV IPv6 failed. Try EPSV IPv4 */
6b679a01
AJ
2557 if (ftpState->ctrl.conn->local.IsIPv4()) {
2558 debugs(9, 5, HERE << "FTP Channel is IPv4 (" << ftpState->ctrl.conn->remote << ") attempting EPSV 1 after EPSV ALL has failed.");
903198a7 2559 snprintf(cbuf, CTRL_BUFLEN, "EPSV 1\r\n");
9adb5bc5 2560 ftpState->state = SENT_EPSV_1;
d06d0e1e 2561 break;
e1381638 2562 } else if (ftpState->flags.epsv_all_sent) {
9adb5bc5
AJ
2563 debugs(9, DBG_IMPORTANT, "FTP does not allow PASV method after 'EPSV ALL' has been sent.");
2564 ftpFail(ftpState);
2565 return;
2566 }
2567 // else fall through to skip EPSV 1
a689bd4e 2568
9adb5bc5 2569 case SENT_EPSV_1: /* EPSV options exhausted. Try PASV now. */
6b679a01 2570 debugs(9, 5, HERE << "FTP Channel (" << ftpState->ctrl.conn->remote << ") rejects EPSV connection attempts. Trying PASV instead.");
903198a7 2571 snprintf(cbuf, CTRL_BUFLEN, "PASV\r\n");
9adb5bc5 2572 ftpState->state = SENT_PASV;
a689bd4e 2573 break;
2574
2575 default:
51ee534d 2576 if (!Config.Ftp.epsv) {
6b679a01 2577 debugs(9, 5, HERE << "EPSV support manually disabled. Sending PASV for FTP Channel (" << ftpState->ctrl.conn->remote <<")");
903198a7 2578 snprintf(cbuf, CTRL_BUFLEN, "PASV\r\n");
51ee534d
AJ
2579 ftpState->state = SENT_PASV;
2580 } else if (Config.Ftp.epsv_all) {
6b679a01 2581 debugs(9, 5, HERE << "EPSV ALL manually enabled. Attempting with FTP Channel (" << ftpState->ctrl.conn->remote <<")");
903198a7 2582 snprintf(cbuf, CTRL_BUFLEN, "EPSV ALL\r\n");
a689bd4e 2583 ftpState->state = SENT_EPSV_ALL;
2584 /* block other non-EPSV connections being attempted */
2585 ftpState->flags.epsv_all_sent = true;
9e008dda 2586 } else {
6b679a01
AJ
2587 if (ftpState->ctrl.conn->local.IsIPv6()) {
2588 debugs(9, 5, HERE << "FTP Channel (" << ftpState->ctrl.conn->remote << "). Sending default EPSV 2");
903198a7 2589 snprintf(cbuf, CTRL_BUFLEN, "EPSV 2\r\n");
fe2d668b
AJ
2590 ftpState->state = SENT_EPSV_2;
2591 }
6b679a01
AJ
2592 if (ftpState->ctrl.conn->local.IsIPv4()) {
2593 debugs(9, 5, HERE << "Channel (" << ftpState->ctrl.conn->remote <<"). Sending default EPSV 1");
903198a7 2594 snprintf(cbuf, CTRL_BUFLEN, "EPSV 1\r\n");
fe2d668b
AJ
2595 ftpState->state = SENT_EPSV_1;
2596 }
a689bd4e 2597 }
2598 break;
2599 }
62e76326 2600
a689bd4e 2601 ftpState->writeCommand(cbuf);
62e76326 2602
7e3ce7b9 2603 /*
2604 * ugly hack for ftp servers like ftp.netscape.com that sometimes
903198a7 2605 * dont acknowledge PASV commands. Use connect timeout to be faster then read timeout (minutes).
7e3ce7b9 2606 */
dc56a9b1 2607 typedef CommCbMemFunT<FtpStateData, CommTimeoutCbParams> TimeoutDialer;
4299f876 2608 AsyncCall::Pointer timeoutCall = JobCallback(9, 5,
4cb2536f 2609 TimeoutDialer, ftpState, FtpStateData::ftpTimeout);
8d77a37c 2610 commSetConnTimeout(ftpState->ctrl.conn, Config.Timeout.connect, timeoutCall);
3fdadc70 2611}
2612
063a47b5 2613void
2614FtpStateData::processHeadResponse()
2615{
2616 debugs(9, 5, HERE << "handling HEAD response");
2617 ftpSendQuit(this);
2618 appendSuccessHeader();
2619
2620 /*
2621 * On rare occasions I'm seeing the entry get aborted after
2622 * ftpReadControlReply() and before here, probably when
2623 * trying to write to the client.
2624 */
2625 if (EBIT_TEST(entry->flags, ENTRY_ABORTED)) {
2626 abortTransaction("entry aborted while processing HEAD");
2627 return;
2628 }
2629
a83c6ed6
AR
2630#if USE_ADAPTATION
2631 if (adaptationAccessCheckPending) {
2632 debugs(9,3, HERE << "returning due to adaptationAccessCheckPending");
063a47b5 2633 return;
2634 }
2635#endif
2636
2637 // processReplyBody calls serverComplete() since there is no body
9e008dda 2638 processReplyBody();
063a47b5 2639}
2640
63be0a78 2641/// \ingroup ServerProtocolFTPInternal
3fdadc70 2642static void
2643ftpReadPasv(FtpStateData * ftpState)
2644{
2645 int code = ftpState->ctrl.replycode;
2646 int h1, h2, h3, h4;
2647 int p1, p2;
2648 int n;
f45dd259 2649 unsigned short port;
b7ac5457 2650 Ip::Address ipa_remote;
748f6a15 2651 char *buf;
00c5afca 2652 LOCAL_ARRAY(char, ipaddr, 1024);
a689bd4e 2653 debugs(9, 3, HERE);
62e76326 2654
3fdadc70 2655 if (code != 227) {
a689bd4e 2656 debugs(9, 2, "PASV not supported by remote end");
cc192b50 2657 ftpSendEPRT(ftpState);
62e76326 2658 return;
3fdadc70 2659 }
62e76326 2660
748f6a15 2661 /* 227 Entering Passive Mode (h1,h2,h3,h4,p1,p2). */
2662 /* ANSI sez [^0-9] is undefined, it breaks on Watcom cc */
a689bd4e 2663 debugs(9, 5, HERE << "scanning: " << ftpState->ctrl.last_reply);
62e76326 2664
3f9d0b2d 2665 buf = ftpState->ctrl.last_reply + strcspn(ftpState->ctrl.last_reply, "0123456789");
62e76326 2666
748f6a15 2667 n = sscanf(buf, "%d,%d,%d,%d,%d,%d", &h1, &h2, &h3, &h4, &p1, &p2);
62e76326 2668
748f6a15 2669 if (n != 6 || p1 < 0 || p2 < 0 || p1 > 255 || p2 > 255) {
a689bd4e 2670 debugs(9, DBG_IMPORTANT, "Unsafe PASV reply from " <<
6b679a01 2671 ftpState->ctrl.conn->remote << ": " <<
bf8fe701 2672 ftpState->ctrl.last_reply);
2673
cc192b50 2674 ftpSendEPRT(ftpState);
62e76326 2675 return;
3fdadc70 2676 }
62e76326 2677
00c5afca 2678 snprintf(ipaddr, 1024, "%d.%d.%d.%d", h1, h2, h3, h4);
62e76326 2679
cc192b50 2680 ipa_remote = ipaddr;
2681
9e008dda 2682 if ( ipa_remote.IsAnyAddr() ) {
a689bd4e 2683 debugs(9, DBG_IMPORTANT, "Unsafe PASV reply from " <<
6b679a01 2684 ftpState->ctrl.conn->remote << ": " <<
bf8fe701 2685 ftpState->ctrl.last_reply);
2686
cc192b50 2687 ftpSendEPRT(ftpState);
62e76326 2688 return;
3fdadc70 2689 }
62e76326 2690
3fdadc70 2691 port = ((p1 << 8) + p2);
62e76326 2692
7f74df3a 2693 if (0 == port) {
a689bd4e 2694 debugs(9, DBG_IMPORTANT, "Unsafe PASV reply from " <<
6b679a01 2695 ftpState->ctrl.conn->remote << ": " <<
bf8fe701 2696 ftpState->ctrl.last_reply);
2697
cc192b50 2698 ftpSendEPRT(ftpState);
62e76326 2699 return;
7f74df3a 2700 }
62e76326 2701
00c5afca 2702 if (Config.Ftp.sanitycheck) {
62e76326 2703 if (port < 1024) {
a689bd4e 2704 debugs(9, DBG_IMPORTANT, "Unsafe PASV reply from " <<
6b679a01 2705 ftpState->ctrl.conn->remote << ": " <<
bf8fe701 2706 ftpState->ctrl.last_reply);
2707
cc192b50 2708 ftpSendEPRT(ftpState);
62e76326 2709 return;
2710 }
00c5afca 2711 }
62e76326 2712
9b312a19 2713 ftpState->data.port = port;
3f9d0b2d 2714
2715 if (Config.Ftp.sanitycheck)
6b679a01 2716 ftpState->data.host = xstrdup(fd_table[ftpState->ctrl.conn->fd].ipaddr);
3f9d0b2d 2717 else
2718 ftpState->data.host = xstrdup(ipaddr);
2719
9bc73deb 2720 safe_free(ftpState->ctrl.last_command);
3f9d0b2d 2721
9bc73deb 2722 safe_free(ftpState->ctrl.last_reply);
3f9d0b2d 2723
9bc73deb 2724 ftpState->ctrl.last_command = xstrdup("Connect to server data port");
3f9d0b2d 2725
f9b72e0c 2726 Comm::ConnectionPointer conn = new Comm::Connection;
6b679a01 2727 conn->local = ftpState->ctrl.conn->local;
0a085c25 2728 conn->local.SetPort(0);
cfd66529
AJ
2729 conn->remote = ipaddr;
2730 conn->remote.SetPort(port);
6b679a01
AJ
2731
2732 debugs(9, 3, HERE << "connecting to " << conn->remote);
cfd66529 2733
e3a4aecc
AJ
2734 ftpState->data.opener = commCbCall(9,3, "FtpStateData::ftpPasvCallback", CommConnectCbPtrFun(FtpStateData::ftpPasvCallback, ftpState));
2735 Comm::ConnOpener *cs = new Comm::ConnOpener(conn, ftpState->data.opener, Config.Timeout.connect);
aed188fd 2736 cs->setHost(ftpState->data.host);
855150a4 2737 AsyncJob::Start(cs);
3fdadc70 2738}
2739
6f0aab86 2740void
f01d4b80 2741FtpStateData::ftpPasvCallback(const Comm::ConnectionPointer &conn, comm_err_t status, int xerrno, void *data)
3fdadc70 2742{
e6ccf245 2743 FtpStateData *ftpState = (FtpStateData *)data;
a689bd4e 2744 debugs(9, 3, HERE);
e3a4aecc 2745 ftpState->data.opener = NULL;
62e76326 2746
9b312a19 2747 if (status != COMM_OK) {
6200d3d6 2748 debugs(9, 2, HERE << "Failed to connect. Retrying via another method.");
6200d3d6
AJ
2749
2750 // ABORT on timeouts. server may be waiting on a broken TCP link.
2751 if (status == COMM_TIMEOUT)
2752 ftpState->writeCommand("ABOR");
2753
2754 // try another connection attempt with some other method
2755 ftpSendPassive(ftpState);
62e76326 2756 return;
3fdadc70 2757 }
62e76326 2758
6200d3d6 2759 ftpState->data.opened(conn, ftpState->dataCloser());
3fdadc70 2760 ftpRestOrList(ftpState);
2761}
2762
63be0a78 2763/// \ingroup ServerProtocolFTPInternal
903198a7 2764static void
cdc33f35 2765ftpOpenListenSocket(FtpStateData * ftpState, int fallback)
2766{
04f55905 2767 /// Close old data channels, if any. We may open a new one below.
8bbb16e3 2768 if ((ftpState->data.conn->flags & COMM_REUSEADDR))
cbff89ba
AJ
2769 // NP: in fact it points to the control channel. just clear it.
2770 ftpState->data.clear();
2771 else
2772 ftpState->data.close();
00406b24 2773 ftpState->data.host = NULL;
62e76326 2774
4f310655 2775 /*
2776 * Set up a listen socket on the same local address as the
2777 * control connection.
2778 */
8bbb16e3
AJ
2779 Comm::ConnectionPointer temp = new Comm::Connection;
2780 temp->local = ftpState->ctrl.conn->local;
62e76326 2781
4f310655 2782 /*
2783 * REUSEADDR is needed in fallback mode, since the same port is
2784 * used for both control and data.
cdc33f35 2785 */
2786 if (fallback) {
6200d3d6 2787 int on = 1;
6b679a01
AJ
2788 setsockopt(ftpState->ctrl.conn->fd, SOL_SOCKET, SO_REUSEADDR, (char *) &on, sizeof(on));
2789 ftpState->ctrl.conn->flags |= COMM_REUSEADDR;
8bbb16e3 2790 temp->flags |= COMM_REUSEADDR;
9e008dda 2791 } else {
a689bd4e 2792 /* if not running in fallback mode a new port needs to be retrieved */
8bbb16e3 2793 temp->local.SetPort(0);
cdc33f35 2794 }
62e76326 2795
8bbb16e3 2796 ftpState->listenForDataChannel(temp, ftpState->entry->url());
cdc33f35 2797}
2798
63be0a78 2799/// \ingroup ServerProtocolFTPInternal
3fdadc70 2800static void
cc192b50 2801ftpSendPORT(FtpStateData * ftpState)
3fdadc70 2802{
a11382aa 2803 /* check the server control channel is still available */
9e008dda 2804 if (!ftpState || !ftpState->haveControlChannel("ftpSendPort"))
a11382aa 2805 return;
2806
9e008dda 2807 if (Config.Ftp.epsv_all && ftpState->flags.epsv_all_sent) {
a689bd4e 2808 debugs(9, DBG_IMPORTANT, "FTP does not allow PORT method after 'EPSV ALL' has been sent.");
2809 return;
2810 }
2811
2812 debugs(9, 3, HERE);
e55f0142 2813 ftpState->flags.pasv_supported = 0;
903198a7 2814 ftpOpenListenSocket(ftpState, 0);
6b679a01 2815
00406b24
AJ
2816 if (!Comm::IsConnOpen(ftpState->data.listenConn)) {
2817 if ( ftpState->data.listenConn != NULL && !ftpState->data.listenConn->local.IsIPv4() ) {
6b679a01
AJ
2818 /* non-IPv4 CANNOT send PORT command. */
2819 /* we got here by attempting and failing an EPRT */
2820 /* using the same reply code should simulate a PORT failure */
2821 ftpReadPORT(ftpState);
2822 return;
2823 }
bf8fe701 2824
62e76326 2825 /* XXX Need to set error message */
2826 ftpFail(ftpState);
2827 return;
cdc33f35 2828 }
62e76326 2829
6200d3d6
AJ
2830 // pull out the internal IP address bytes to send in PORT command...
2831 // source them from the listen_conn->local
cc192b50 2832
6200d3d6 2833 struct addrinfo *AI = NULL;
00406b24 2834 ftpState->data.listenConn->local.GetAddrInfo(AI, AF_INET);
6200d3d6
AJ
2835 unsigned char *addrptr = (unsigned char *) &((struct sockaddr_in*)AI->ai_addr)->sin_addr;
2836 unsigned char *portptr = (unsigned char *) &((struct sockaddr_in*)AI->ai_addr)->sin_port;
903198a7 2837 snprintf(cbuf, CTRL_BUFLEN, "PORT %d,%d,%d,%d,%d,%d\r\n",
62e76326 2838 addrptr[0], addrptr[1], addrptr[2], addrptr[3],
2839 portptr[0], portptr[1]);
2f47fadf 2840 ftpState->writeCommand(cbuf);
cdc33f35 2841 ftpState->state = SENT_PORT;
cc192b50 2842
00406b24 2843 ftpState->data.listenConn->local.FreeAddrInfo(AI);
3fdadc70 2844}
2845
63be0a78 2846/// \ingroup ServerProtocolFTPInternal
3fdadc70 2847static void
cc192b50 2848ftpReadPORT(FtpStateData * ftpState)
3fdadc70 2849{
cdc33f35 2850 int code = ftpState->ctrl.replycode;
a689bd4e 2851 debugs(9, 3, HERE);
62e76326 2852
cdc33f35 2853 if (code != 200) {
62e76326 2854 /* Fall back on using the same port as the control connection */
bf8fe701 2855 debugs(9, 3, "PORT not supported by remote end");
62e76326 2856 ftpOpenListenSocket(ftpState, 1);
cdc33f35 2857 }
62e76326 2858
cdc33f35 2859 ftpRestOrList(ftpState);
2860}
2861
63be0a78 2862/// \ingroup ServerProtocolFTPInternal
cc192b50 2863static void
2864ftpSendEPRT(FtpStateData * ftpState)
2865{
9e008dda 2866 if (Config.Ftp.epsv_all && ftpState->flags.epsv_all_sent) {
a689bd4e 2867 debugs(9, DBG_IMPORTANT, "FTP does not allow EPRT method after 'EPSV ALL' has been sent.");
2868 return;
2869 }
2870
63ee5443
AJ
2871 if (!Config.Ftp.eprt) {
2872 /* Disabled. Switch immediately to attempting old PORT command. */
2873 debugs(9, 3, "EPRT disabled by local administrator");
2874 ftpSendPORT(ftpState);
2875 return;
2876 }
2877
a689bd4e 2878 debugs(9, 3, HERE);
cc192b50 2879 ftpState->flags.pasv_supported = 0;
903198a7
AJ
2880
2881 ftpOpenListenSocket(ftpState, 0);
8bbb16e3
AJ
2882 debugs(9, 3, "Listening for FTP data connection with FD " << ftpState->data.conn);
2883 if (!Comm::IsConnOpen(ftpState->data.conn)) {
903198a7
AJ
2884 /* XXX Need to set error message */
2885 ftpFail(ftpState);
2886 return;
2887 }
cc192b50 2888
8bbb16e3 2889 char buf[MAX_IPSTRLEN];
cc192b50 2890
2891 /* RFC 2428 defines EPRT as IPv6 equivalent to IPv4 PORT command. */
2892 /* Which can be used by EITHER protocol. */
903198a7 2893 snprintf(cbuf, CTRL_BUFLEN, "EPRT |%d|%s|%d|\r\n",
00406b24
AJ
2894 ( ftpState->data.listenConn->local.IsIPv6() ? 2 : 1 ),
2895 ftpState->data.listenConn->local.NtoA(buf,MAX_IPSTRLEN),
2896 ftpState->data.listenConn->local.GetPort() );
cc192b50 2897
2898 ftpState->writeCommand(cbuf);
2899 ftpState->state = SENT_EPRT;
cc192b50 2900}
2901
2902static void
2903ftpReadEPRT(FtpStateData * ftpState)
2904{
2905 int code = ftpState->ctrl.replycode;
a689bd4e 2906 debugs(9, 3, HERE);
cc192b50 2907
2908 if (code != 200) {
2909 /* Failover to attempting old PORT command. */
2910 debugs(9, 3, "EPRT not supported by remote end");
2911 ftpSendPORT(ftpState);
2912 return;
2913 }
2914
2915 ftpRestOrList(ftpState);
2916}
2917
a689bd4e 2918/**
63be0a78 2919 \ingroup ServerProtocolFTPInternal
a689bd4e 2920 \par
2921 * "read" handler to accept FTP data connections.
2922 *
63be0a78 2923 \param io comm accept(2) callback parameters
a689bd4e 2924 */
6200d3d6
AJ
2925void
2926FtpStateData::ftpAcceptDataConnection(const CommAcceptCbParams &io)
cdc33f35 2927{
cbff89ba 2928 debugs(9, 3, HERE);
cdc33f35 2929
dc56a9b1 2930 if (EBIT_TEST(entry->flags, ENTRY_ABORTED)) {
2931 abortTransaction("entry aborted when accepting data conn");
00406b24
AJ
2932 data.listenConn->close();
2933 data.listenConn = NULL;
62e76326 2934 return;
b044a7eb 2935 }
c4b7a5a9 2936
903198a7 2937 if (io.flag != COMM_OK) {
00406b24
AJ
2938 data.listenConn->close();
2939 data.listenConn = NULL;
2940 debugs(9, DBG_IMPORTANT, "FTP AcceptDataConnection: " << io.conn << ": " << xstrerr(io.xerrno));
2941 /** \todo Need to send error message on control channel*/
903198a7
AJ
2942 ftpFail(this);
2943 return;
2944 }
2945
00406b24
AJ
2946 /* data listening conn is no longer even open. abort. */
2947 if (!Comm::IsConnOpen(data.listenConn)) {
2948 data.listenConn = NULL; // ensure that it's cleared and not just closed.
2949 return;
2950 }
2951
cbff89ba 2952 /* data listening conn is no longer even open. abort. */
8bbb16e3 2953 if (!Comm::IsConnOpen(data.conn)) {
cbff89ba
AJ
2954 data.clear(); // ensure that it's cleared and not just closed.
2955 return;
2956 }
2957
a689bd4e 2958 /** \par
2959 * When squid.conf ftp_sanitycheck is enabled, check the new connection is actually being
2960 * made by the remote client which is connected to the FTP control socket.
00406b24 2961 * Or the one which we were told to listen for by control channel messages (may differ under NAT).
a689bd4e 2962 * This prevents third-party hacks, but also third-party load balancing handshakes.
2963 */
00c5afca 2964 if (Config.Ftp.sanitycheck) {
6200d3d6 2965 // accept if either our data or ctrl connection is talking to this remote peer.
8bbb16e3 2966 if (data.conn->remote != io.conn->remote && ctrl.conn->remote != io.conn->remote) {
9e008dda
AJ
2967 debugs(9, DBG_IMPORTANT,
2968 "FTP data connection from unexpected server (" <<
00406b24 2969 io.conn->remote << "), expecting " <<
8bbb16e3 2970 data.conn->remote << " or " << ctrl.conn->remote);
dc56a9b1 2971
cbff89ba 2972 /* close the bad sources connection down ASAP. */
8bbb16e3 2973 io.conn->close();
04f55905 2974
00406b24 2975 /* drop the bad connection (io) by ignoring the attempt. */
62e76326 2976 return;
2977 }
00c5afca 2978 }
62e76326 2979
903198a7 2980 /** On COMM_OK start using the accepted data socket and discard the temporary listen socket. */
94b88585 2981 data.close();
00406b24
AJ
2982 data.opened(io.conn, dataCloser());
2983 io.conn->remote.NtoA(data.host,SQUIDHOSTNAMELEN);
a689bd4e 2984
903198a7 2985 debugs(9, 3, HERE << "Connected data socket on " <<
00406b24 2986 io.conn << ". FD table says: " <<
6b679a01
AJ
2987 "ctrl-peer= " << fd_table[ctrl.conn->fd].ipaddr << ", " <<
2988 "data-peer= " << fd_table[data.conn->fd].ipaddr);
a689bd4e 2989
903198a7
AJ
2990 assert(haveControlChannel("ftpAcceptDataConnection"));
2991 assert(ctrl.message == NULL);
a689bd4e 2992
903198a7 2993 // Ctrl channel operations will determine what happens to this data connection
3fdadc70 2994}
2995
63be0a78 2996/// \ingroup ServerProtocolFTPInternal
3fdadc70 2997static void
2998ftpRestOrList(FtpStateData * ftpState)
2999{
a689bd4e 3000 debugs(9, 3, HERE);
62e76326 3001
94439e4e 3002 if (ftpState->typecode == 'D') {
62e76326 3003 ftpState->flags.isdir = 1;
62e76326 3004
3005 if (ftpState->flags.put) {
3006 ftpSendMkdir(ftpState); /* PUT name;type=d */
3007 } else {
3008 ftpSendNlst(ftpState); /* GET name;type=d sec 3.2.2 of RFC 1738 */
3009 }
94439e4e 3010 } else if (ftpState->flags.put) {
62e76326 3011 ftpSendStor(ftpState);
e55f0142 3012 } else if (ftpState->flags.isdir)
62e76326 3013 ftpSendList(ftpState);
6f0aab86 3014 else if (ftpState->restartable())
62e76326 3015 ftpSendRest(ftpState);
969c39b9 3016 else
62e76326 3017 ftpSendRetr(ftpState);
969c39b9 3018}
3019
63be0a78 3020/// \ingroup ServerProtocolFTPInternal
54220df8 3021static void
3022ftpSendStor(FtpStateData * ftpState)
3023{
a11382aa 3024 /* check the server control channel is still available */
9e008dda 3025 if (!ftpState || !ftpState->haveControlChannel("ftpSendStor"))
a11382aa 3026 return;
3027
a689bd4e 3028 debugs(9, 3, HERE);
3029
9bc73deb 3030 if (ftpState->filepath != NULL) {
62e76326 3031 /* Plain file upload */
903198a7 3032 snprintf(cbuf, CTRL_BUFLEN, "STOR %s\r\n", ftpState->filepath);
2f47fadf 3033 ftpState->writeCommand(cbuf);
62e76326 3034 ftpState->state = SENT_STOR;
47f6e231 3035 } else if (ftpState->request->header.getInt64(HDR_CONTENT_LENGTH) > 0) {
62e76326 3036 /* File upload without a filename. use STOU to generate one */
903198a7 3037 snprintf(cbuf, CTRL_BUFLEN, "STOU\r\n");
2f47fadf 3038 ftpState->writeCommand(cbuf);
62e76326 3039 ftpState->state = SENT_STOR;
9bc73deb 3040 } else {
62e76326 3041 /* No file to transfer. Only create directories if needed */
3042 ftpSendReply(ftpState);
9bc73deb 3043 }
54220df8 3044}
3045
63be0a78 3046/// \ingroup ServerProtocolFTPInternal
3047/// \deprecated use ftpState->readStor() instead.
54220df8 3048static void
3049ftpReadStor(FtpStateData * ftpState)
3050{
5f8252d2 3051 ftpState->readStor();
3052}
3053
9e008dda
AJ
3054void FtpStateData::readStor()
3055{
5f8252d2 3056 int code = ctrl.replycode;
a689bd4e 3057 debugs(9, 3, HERE);
62e76326 3058
d890b809 3059 if (code == 125 || (code == 150 && Comm::IsConnOpen(data.conn))) {
123ec4de 3060 if (!startRequestBodyFlow()) { // register to receive body data
5f8252d2 3061 ftpFail(this);
3062 return;
3063 }
3064
d890b809 3065 /* When client status is 125, or 150 and the data connection is open, Begin data transfer. */
a689bd4e 3066 debugs(9, 3, HERE << "starting data transfer");
d890b809 3067 switchTimeoutToDataChannel();
5f8252d2 3068 sendMoreRequestBody();
d890b809 3069 fwd->dontRetry(true); // dont permit re-trying if the body was sent.
5f8252d2 3070 state = WRITING_DATA;
a689bd4e 3071 debugs(9, 3, HERE << "writing data channel");
9bc73deb 3072 } else if (code == 150) {
d890b809 3073 /* When client code is 150 with no data channel, Accept data channel. */
dc56a9b1 3074 debugs(9, 3, "ftpReadStor: accepting data channel");
00406b24 3075 listenForDataChannel(data.conn, data.host);
54220df8 3076 } else {
a689bd4e 3077 debugs(9, DBG_IMPORTANT, HERE << "Unexpected reply code "<< std::setfill('0') << std::setw(3) << code);
5f8252d2 3078 ftpFail(this);
54220df8 3079 }
3080}
3081
63be0a78 3082/// \ingroup ServerProtocolFTPInternal
969c39b9 3083static void
3084ftpSendRest(FtpStateData * ftpState)
3085{
a11382aa 3086 /* check the server control channel is still available */
9e008dda 3087 if (!ftpState || !ftpState->haveControlChannel("ftpSendRest"))
a11382aa 3088 return;
3089
a689bd4e 3090 debugs(9, 3, HERE);
3091
903198a7 3092 snprintf(cbuf, CTRL_BUFLEN, "REST %"PRId64"\r\n", ftpState->restart_offset);
2f47fadf 3093 ftpState->writeCommand(cbuf);
969c39b9 3094 ftpState->state = SENT_REST;
3fdadc70 3095}
3096
6f0aab86 3097int
3098FtpStateData::restartable()
cfbf5373 3099{
6f0aab86 3100 if (restart_offset > 0)
62e76326 3101 return 1;
3102
6f0aab86 3103 if (!request->range)
62e76326 3104 return 0;
3105
6f0aab86 3106 if (!flags.binary)
62e76326 3107 return 0;
3108
47f6e231 3109 if (theSize <= 0)
62e76326 3110 return 0;
cfbf5373 3111
47f6e231 3112 int64_t desired_offset = request->range->lowestOffset(theSize);
62e76326 3113
b0e52cb9 3114 if (desired_offset <= 0)
62e76326 3115 return 0;
3116
47f6e231 3117 if (desired_offset >= theSize)
9e008dda 3118 return 0;
b0e52cb9 3119
3120 restart_offset = desired_offset;
cfbf5373 3121 return 1;
3122}
3123
63be0a78 3124/// \ingroup ServerProtocolFTPInternal
3fdadc70 3125static void
3126ftpReadRest(FtpStateData * ftpState)
3127{
3128 int code = ftpState->ctrl.replycode;
a689bd4e 3129 debugs(9, 3, HERE);
3fdadc70 3130 assert(ftpState->restart_offset > 0);
62e76326 3131
3fdadc70 3132 if (code == 350) {
04f7fd38 3133 ftpState->setCurrentOffset(ftpState->restart_offset);
62e76326 3134 ftpSendRetr(ftpState);
3fdadc70 3135 } else if (code > 0) {
a689bd4e 3136 debugs(9, 3, HERE << "REST not supported");
62e76326 3137 ftpState->flags.rest_supported = 0;
3138 ftpSendRetr(ftpState);
3fdadc70 3139 } else {
62e76326 3140 ftpFail(ftpState);
3fdadc70 3141 }
3142}
3143
63be0a78 3144/// \ingroup ServerProtocolFTPInternal
969c39b9 3145static void
3146ftpSendList(FtpStateData * ftpState)
3147{
a11382aa 3148 /* check the server control channel is still available */
9e008dda 3149 if (!ftpState || !ftpState->haveControlChannel("ftpSendList"))
a11382aa 3150 return;
3151
a689bd4e 3152 debugs(9, 3, HERE);
3153
dbfed404 3154 if (ftpState->filepath) {
903198a7 3155 snprintf(cbuf, CTRL_BUFLEN, "LIST %s\r\n", ftpState->filepath);
dbfed404 3156 } else {
903198a7 3157 snprintf(cbuf, CTRL_BUFLEN, "LIST\r\n");
dbfed404 3158 }
62e76326 3159
2f47fadf 3160 ftpState->writeCommand(cbuf);
969c39b9 3161 ftpState->state = SENT_LIST;
3162}
3163
63be0a78 3164/// \ingroup ServerProtocolFTPInternal
dbfed404 3165static void
3166ftpSendNlst(FtpStateData * ftpState)
3167{
a11382aa 3168 /* check the server control channel is still available */
9e008dda 3169 if (!ftpState || !ftpState->haveControlChannel("ftpSendNlst"))
a11382aa 3170 return;
3171
a689bd4e 3172 debugs(9, 3, HERE);
3173
e55f0142 3174 ftpState->flags.tried_nlst = 1;
62e76326 3175
dbfed404 3176 if (ftpState->filepath) {
903198a7 3177 snprintf(cbuf, CTRL_BUFLEN, "NLST %s\r\n", ftpState->filepath);
dbfed404 3178 } else {
903198a7 3179 snprintf(cbuf, CTRL_BUFLEN, "NLST\r\n");
dbfed404 3180 }
62e76326 3181
2f47fadf 3182 ftpState->writeCommand(cbuf);
dbfed404 3183 ftpState->state = SENT_NLST;
3184}
3185
63be0a78 3186/// \ingroup ServerProtocolFTPInternal
3fdadc70 3187static void
3188ftpReadList(FtpStateData * ftpState)
3189{
3190 int code = ftpState->ctrl.replycode;
a689bd4e 3191 debugs(9, 3, HERE);
62e76326 3192
6200d3d6 3193 if (code == 125 || (code == 150 && Comm::IsConnOpen(ftpState->data.conn))) {
62e76326 3194 /* Begin data transfer */
8bbb16e3 3195 debugs(9, 3, HERE << "begin data transfer from " << ftpState->data.conn->remote << " (" << ftpState->data.conn->local << ")");
6200d3d6 3196 ftpState->switchTimeoutToDataChannel();
5f8252d2 3197 ftpState->maybeReadVirginBody();
62e76326 3198 ftpState->state = READING_DATA;
62e76326 3199 return;
cdc33f35 3200 } else if (code == 150) {
62e76326 3201 /* Accept data channel */
8bbb16e3 3202 debugs(9, 3, HERE << "accept data channel from " << ftpState->data.conn->remote << " (" << ftpState->data.conn->local << ")");
00406b24 3203 ftpState->listenForDataChannel(ftpState->data.conn, ftpState->data.host);
62e76326 3204 return;
e55f0142 3205 } else if (!ftpState->flags.tried_nlst && code > 300) {
62e76326 3206 ftpSendNlst(ftpState);
3fdadc70 3207 } else {
62e76326 3208 ftpFail(ftpState);
3209 return;
3fdadc70 3210 }
3211}
3212
63be0a78 3213/// \ingroup ServerProtocolFTPInternal
969c39b9 3214static void
3215ftpSendRetr(FtpStateData * ftpState)
3216{
a11382aa 3217 /* check the server control channel is still available */
9e008dda 3218 if (!ftpState || !ftpState->haveControlChannel("ftpSendRetr"))
a11382aa 3219 return;
3220
a689bd4e 3221 debugs(9, 3, HERE);
3222
969c39b9 3223 assert(ftpState->filepath != NULL);
903198a7 3224 snprintf(cbuf, CTRL_BUFLEN, "RETR %s\r\n", ftpState->filepath);
2f47fadf 3225 ftpState->writeCommand(cbuf);
969c39b9 3226 ftpState->state = SENT_RETR;
3227}
3228
63be0a78 3229/// \ingroup ServerProtocolFTPInternal
3fdadc70 3230static void
3231ftpReadRetr(FtpStateData * ftpState)
3232{
3233 int code = ftpState->ctrl.replycode;
a689bd4e 3234 debugs(9, 3, HERE);
62e76326 3235
6200d3d6 3236 if (code == 125 || (code == 150 && Comm::IsConnOpen(ftpState->data.conn))) {
62e76326 3237 /* Begin data transfer */
48e7baac 3238 debugs(9, 3, HERE << "begin data transfer from " << ftpState->data.conn->remote << " (" << ftpState->data.conn->local << ")");
6200d3d6 3239 ftpState->switchTimeoutToDataChannel();
5f8252d2 3240 ftpState->maybeReadVirginBody();
62e76326 3241 ftpState->state = READING_DATA;
cdc33f35 3242 } else if (code == 150) {
62e76326 3243 /* Accept data channel */
00406b24 3244 ftpState->listenForDataChannel(ftpState->data.conn, ftpState->data.host);
cdc33f35 3245 } else if (code >= 300) {
62e76326 3246 if (!ftpState->flags.try_slash_hack) {
3247 /* Try this as a directory missing trailing slash... */
6f0aab86 3248 ftpState->hackShortcut(ftpSendCwd);
62e76326 3249 } else {
3250 ftpFail(ftpState);
3251 }
cdc33f35 3252 } else {
62e76326 3253 ftpFail(ftpState);
3fdadc70 3254 }
3255}
3256
0477a072
AJ
3257/**
3258 * Generate the HTTP headers and template fluff around an FTP
3259 * directory listing display.
3260 */
3261void
3262FtpStateData::completedListing()
3263{
3264 assert(entry);
3265 entry->lock();
913524f0
AJ
3266 ErrorState ferr(ERR_DIR_LISTING, HTTP_OK, request);
3267 ferr.ftp.listing = &listing;
3268 ferr.ftp.cwd_msg = xstrdup(cwd_message.size()? cwd_message.termedBuf() : "");
3269 ferr.ftp.server_msg = ctrl.message;
0477a072 3270 ctrl.message = NULL;
913524f0 3271 entry->replaceHttpReply( ferr.BuildHttpReply() );
0477a072
AJ
3272 EBIT_CLR(entry->flags, ENTRY_FWD_HDR_WAIT);
3273 entry->flush();
3274 entry->unlock();
3275}
3276
3277
63be0a78 3278/// \ingroup ServerProtocolFTPInternal
3fdadc70 3279static void
3280ftpReadTransferDone(FtpStateData * ftpState)
3281{
3282 int code = ftpState->ctrl.replycode;
a689bd4e 3283 debugs(9, 3, HERE);
62e76326 3284
bbf07a94 3285 if (code == 226 || code == 250) {
62e76326 3286 /* Connection closed; retrieval done. */
0477a072
AJ
3287 if (ftpState->flags.listing) {
3288 ftpState->completedListing();
3289 /* QUIT operation handles sending the reply to client */
3290 }
c0d75e74 3291 ftpSendQuit(ftpState);
9bc73deb 3292 } else { /* != 226 */
a689bd4e 3293 debugs(9, DBG_IMPORTANT, HERE << "Got code " << code << " after reading data");
8043ea13 3294 ftpState->failed(ERR_FTP_FAILURE, 0);
6b679a01 3295 /* failed closes ctrl.conn and frees ftpState */
62e76326 3296 return;
3fdadc70 3297 }
3298}
3299
5f8252d2 3300// premature end of the request body
6f0aab86 3301void
5f8252d2 3302FtpStateData::handleRequestBodyProducerAborted()
3fdadc70 3303{
5f8252d2 3304 ServerStateData::handleRequestBodyProducerAborted();
a689bd4e 3305 debugs(9, 3, HERE << "ftpState=" << this);
5f8252d2 3306 failed(ERR_READ_ERROR, 0);
94439e4e 3307}
3308
63be0a78 3309/**
3310 * This will be called when the put write is completed
3311 */
6f0aab86 3312void
dc56a9b1 3313FtpStateData::sentRequestBody(const CommIoCbParams &io)
94439e4e 3314{
dc56a9b1 3315 if (io.size > 0)
3316 kb_incr(&statCounter.server.ftp.kbytes_out, io.size);
3317 ServerStateData::sentRequestBody(io);
94439e4e 3318}
3319
63be0a78 3320/// \ingroup ServerProtocolFTPInternal
94439e4e 3321static void
3322ftpWriteTransferDone(FtpStateData * ftpState)
3323{
3324 int code = ftpState->ctrl.replycode;
a689bd4e 3325 debugs(9, 3, HERE);
62e76326 3326
bbf07a94 3327 if (!(code == 226 || code == 250)) {
a689bd4e 3328 debugs(9, DBG_IMPORTANT, HERE << "Got code " << code << " after sending data");
8043ea13 3329 ftpState->failed(ERR_FTP_PUT_ERROR, 0);
62e76326 3330 return;
94439e4e 3331 }
62e76326 3332
3900307b 3333 ftpState->entry->timestampsSet(); /* XXX Is this needed? */
94439e4e 3334 ftpSendReply(ftpState);
969c39b9 3335}
3336
63be0a78 3337/// \ingroup ServerProtocolFTPInternal
969c39b9 3338static void
3339ftpSendQuit(FtpStateData * ftpState)
3340{
a11382aa 3341 /* check the server control channel is still available */
9e008dda 3342 if (!ftpState || !ftpState->haveControlChannel("ftpSendQuit"))
a11382aa 3343 return;
3344
903198a7 3345 snprintf(cbuf, CTRL_BUFLEN, "QUIT\r\n");
2f47fadf 3346 ftpState->writeCommand(cbuf);
3fdadc70 3347 ftpState->state = SENT_QUIT;
3348}
3349
0477a072
AJ
3350/**
3351 * \ingroup ServerProtocolFTPInternal
3352 *
3353 * This completes a client FTP operation with success or other page
3354 * generated and stored in the entry field by the code issuing QUIT.
3355 */
3fdadc70 3356static void
3357ftpReadQuit(FtpStateData * ftpState)
3358{
5f8252d2 3359 ftpState->serverComplete();
3fdadc70 3360}
3361
63be0a78 3362/// \ingroup ServerProtocolFTPInternal
969c39b9 3363static void
3364ftpTrySlashHack(FtpStateData * ftpState)
3365{
3366 char *path;
e55f0142 3367 ftpState->flags.try_slash_hack = 1;
969c39b9 3368 /* Free old paths */
62e76326 3369
a689bd4e 3370 debugs(9, 3, HERE);
3371
969c39b9 3372 if (ftpState->pathcomps)
62e76326 3373 wordlistDestroy(&ftpState->pathcomps);
3374
969c39b9 3375 safe_free(ftpState->filepath);
62e76326 3376
969c39b9 3377 /* Build the new path (urlpath begins with /) */
d53b3f6d 3378 path = xstrdup(ftpState->request->urlpath.termedBuf());
62e76326 3379
969c39b9 3380 rfc1738_unescape(path);
62e76326 3381
969c39b9 3382 ftpState->filepath = path;
62e76326 3383
969c39b9 3384 /* And off we go */
dbfed404 3385 ftpGetFile(ftpState);
969c39b9 3386}
3387
63be0a78 3388/**
3389 * Forget hack status. Next error is shown to the user
3390 */
6f0aab86 3391void
3392FtpStateData::unhack()
0f169992 3393{
a689bd4e 3394 debugs(9, 3, HERE);
3395
6f0aab86 3396 if (old_request != NULL) {
3397 safe_free(old_request);
3398 safe_free(old_reply);
0f169992 3399 }
3400}
3401
6f0aab86 3402void
3403FtpStateData::hackShortcut(FTPSM * nextState)
969c39b9 3404{
c7e0305b 3405 /* Clear some unwanted state */
9fa2e208 3406 setCurrentOffset(0);
6f0aab86 3407 restart_offset = 0;
0f169992 3408 /* Save old error message & some state info */
62e76326 3409
a689bd4e 3410 debugs(9, 3, HERE);
3411
6f0aab86 3412 if (old_request == NULL) {
3413 old_request = ctrl.last_command;
3414 ctrl.last_command = NULL;
3415 old_reply = ctrl.last_reply;
3416 ctrl.last_reply = NULL;
62e76326 3417
6f0aab86 3418 if (pathcomps == NULL && filepath != NULL)
3419 old_filepath = xstrdup(filepath);
0f169992 3420 }
62e76326 3421
969c39b9 3422 /* Jump to the "hack" state */
6f0aab86 3423 nextState(this);
969c39b9 3424}
3425
63be0a78 3426/// \ingroup ServerProtocolFTPInternal
3fdadc70 3427static void
6f0aab86 3428ftpFail(FtpStateData *ftpState)
3fdadc70 3429{
4d6c56a6 3430 debugs(9, 6, HERE << "flags(" <<
9e008dda
AJ
3431 (ftpState->flags.isdir?"IS_DIR,":"") <<
3432 (ftpState->flags.try_slash_hack?"TRY_SLASH_HACK":"") << "), " <<
3433 "mdtm=" << ftpState->mdtm << ", size=" << ftpState->theSize <<
3434 "slashhack=" << (ftpState->request->urlpath.caseCmp("/%2f", 4)==0? "T":"F") );
62e76326 3435
a689bd4e 3436 /* Try the / hack to support "Netscape" FTP URL's for retreiving files */
0cdcddb9 3437 if (!ftpState->flags.isdir && /* Not a directory */
62e76326 3438 !ftpState->flags.try_slash_hack && /* Not in slash hack */
47f6e231 3439 ftpState->mdtm <= 0 && ftpState->theSize < 0 && /* Not known as a file */
30abd221 3440 ftpState->request->urlpath.caseCmp("/%2f", 4) != 0) { /* No slash encoded */
62e76326 3441
3442 switch (ftpState->state) {
3443
3444 case SENT_CWD:
3445
3446 case SENT_RETR:
3447 /* Try the / hack */
6f0aab86 3448 ftpState->hackShortcut(ftpTrySlashHack);
62e76326 3449 return;
3450
3451 default:
3452 break;
3453 }
969c39b9 3454 }
62e76326 3455
6f0aab86 3456 ftpState->failed(ERR_NONE, 0);
6b679a01 3457 /* failed() closes ctrl.conn and frees this */
9bc73deb 3458}
3459
6f0aab86 3460void
3461FtpStateData::failed(err_type error, int xerrno)
9bc73deb 3462{
4d6c56a6 3463 debugs(9,3,HERE << "entry-null=" << (entry?entry->isEmpty():0) << ", entry=" << entry);
528b2c61 3464 if (entry->isEmpty())
6f0aab86 3465 failedErrorMessage(error, xerrno);
62e76326 3466
5f8252d2 3467 serverComplete();
9bc73deb 3468}
3469
6f0aab86 3470void
3471FtpStateData::failedErrorMessage(err_type error, int xerrno)
9bc73deb 3472{
913524f0 3473 ErrorState *ftperr = NULL;
a2c963ae 3474 const char *command, *reply;
c3669e32 3475
b6a2f15e 3476 /* Translate FTP errors into HTTP errors */
9bc73deb 3477 switch (error) {
62e76326 3478
9bc73deb 3479 case ERR_NONE:
62e76326 3480
6f0aab86 3481 switch (state) {
62e76326 3482
3483 case SENT_USER:
3484
3485 case SENT_PASS:
3486
6f0aab86 3487 if (ctrl.replycode > 500)
3488 if (password_url)
913524f0 3489 ftperr = new ErrorState(ERR_FTP_FORBIDDEN, HTTP_FORBIDDEN, fwd->request);
c4a2d5e1 3490 else
913524f0 3491 ftperr = new ErrorState(ERR_FTP_FORBIDDEN, HTTP_UNAUTHORIZED, fwd->request);
c4a2d5e1 3492
6f0aab86 3493 else if (ctrl.replycode == 421)
913524f0 3494 ftperr = new ErrorState(ERR_FTP_UNAVAILABLE, HTTP_SERVICE_UNAVAILABLE, fwd->request);
62e76326 3495
3496 break;
3497
3498 case SENT_CWD:
3499
3500 case SENT_RETR:
6f0aab86 3501 if (ctrl.replycode == 550)
913524f0 3502 ftperr = new ErrorState(ERR_FTP_NOT_FOUND, HTTP_NOT_FOUND, fwd->request);
62e76326 3503
3504 break;
3505
3506 default:
3507 break;
3508 }
3509
3510 break;
3511
9bc73deb 3512 case ERR_READ_TIMEOUT:
913524f0 3513 ftperr = new ErrorState(error, HTTP_GATEWAY_TIMEOUT, fwd->request);
62e76326 3514 break;
3515
b6a2f15e 3516 default:
913524f0 3517 ftperr = new ErrorState(error, HTTP_BAD_GATEWAY, fwd->request);
62e76326 3518 break;
b6a2f15e 3519 }
62e76326 3520
c3669e32 3521 if (ftperr == NULL)
913524f0 3522 ftperr = new ErrorState(ERR_FTP_FAILURE, HTTP_BAD_GATEWAY, fwd->request);
62e76326 3523
c3669e32 3524 ftperr->xerrno = xerrno;
62e76326 3525
c3669e32 3526 ftperr->ftp.server_msg = ctrl.message;
6f0aab86 3527 ctrl.message = NULL;
62e76326 3528
6f0aab86 3529 if (old_request)
3530 command = old_request;
969c39b9 3531 else
6f0aab86 3532 command = ctrl.last_command;
62e76326 3533
9bc73deb 3534 if (command && strncmp(command, "PASS", 4) == 0)
62e76326 3535 command = "PASS <yourpassword>";
3536
6f0aab86 3537 if (old_reply)
3538 reply = old_reply;
969c39b9 3539 else
6f0aab86 3540 reply = ctrl.last_reply;
62e76326 3541
9bc73deb 3542 if (command)
c3669e32 3543 ftperr->ftp.request = xstrdup(command);
62e76326 3544
9bc73deb 3545 if (reply)
c3669e32 3546 ftperr->ftp.reply = xstrdup(reply);
62e76326 3547
c3669e32 3548 entry->replaceHttpReply( ftperr->BuildHttpReply() );
913524f0 3549 delete ftperr;
3fdadc70 3550}
3551
63be0a78 3552/// \ingroup ServerProtocolFTPInternal
54220df8 3553static void
3554ftpSendReply(FtpStateData * ftpState)
3555{
acce49bf 3556 int code = ftpState->ctrl.replycode;
728da2ee 3557 http_status http_code;
3558 err_type err_code = ERR_NONE;
a11382aa 3559
a689bd4e 3560 debugs(9, 3, HERE << ftpState->entry->url() << ", code " << code);
62e76326 3561
fa80a8ef 3562 if (cbdataReferenceValid(ftpState))
a689bd4e 3563 debugs(9, 5, HERE << "ftpState (" << ftpState << ") is valid!");
62e76326 3564
bbf07a94 3565 if (code == 226 || code == 250) {
62e76326 3566 err_code = (ftpState->mdtm > 0) ? ERR_FTP_PUT_MODIFIED : ERR_FTP_PUT_CREATED;
3567 http_code = (ftpState->mdtm > 0) ? HTTP_ACCEPTED : HTTP_CREATED;
9bc73deb 3568 } else if (code == 227) {
62e76326 3569 err_code = ERR_FTP_PUT_CREATED;
3570 http_code = HTTP_CREATED;
54220df8 3571 } else {
62e76326 3572 err_code = ERR_FTP_PUT_ERROR;
3573 http_code = HTTP_INTERNAL_SERVER_ERROR;
54220df8 3574 }
62e76326 3575
64b66b76
CT
3576 if (ftpState->request)
3577 ftpState->request->detailError(err_code, code);
3578
913524f0 3579 ErrorState err(err_code, http_code, ftpState->request);
62e76326 3580
54220df8 3581 if (ftpState->old_request)
913524f0 3582 err.ftp.request = xstrdup(ftpState->old_request);
54220df8 3583 else
913524f0 3584 err.ftp.request = xstrdup(ftpState->ctrl.last_command);
62e76326 3585
54220df8 3586 if (ftpState->old_reply)
913524f0 3587 err.ftp.reply = xstrdup(ftpState->old_reply);
0dfb298f 3588 else if (ftpState->ctrl.last_reply)
913524f0 3589 err.ftp.reply = xstrdup(ftpState->ctrl.last_reply);
0dfb298f 3590 else
913524f0 3591 err.ftp.reply = xstrdup("");
62e76326 3592
913524f0 3593 ftpState->entry->replaceHttpReply( err.BuildHttpReply() );
62e76326 3594
9bc73deb 3595 ftpSendQuit(ftpState);
54220df8 3596}
3597
6f0aab86 3598void
3599FtpStateData::appendSuccessHeader()
3fdadc70 3600{
a2c963ae 3601 const char *mime_type = NULL;
3602 const char *mime_enc = NULL;
30abd221 3603 String urlpath = request->urlpath;
02922e76 3604 const char *filename = NULL;
3605 const char *t = NULL;
253caccb 3606
a689bd4e 3607 debugs(9, 3, HERE);
ee65546f 3608
6f0aab86 3609 if (flags.http_header_sent)
62e76326 3610 return;
3611
585ab260 3612 HttpReply *reply = new HttpReply;
3613
6f0aab86 3614 flags.http_header_sent = 1;
62e76326 3615
0477a072 3616 assert(entry->isEmpty());
62e76326 3617
0477a072 3618 EBIT_CLR(entry->flags, ENTRY_FWD_HDR_WAIT);
62e76326 3619
0477a072 3620 entry->buffer(); /* released when done processing current data payload */
b66315e4 3621
d53b3f6d 3622 filename = (t = urlpath.rpos('/')) ? t + 1 : urlpath.termedBuf();
62e76326 3623
6f0aab86 3624 if (flags.isdir) {
62e76326 3625 mime_type = "text/html";
3fdadc70 3626 } else {
6f0aab86 3627 switch (typecode) {
62e76326 3628
3629 case 'I':
3630 mime_type = "application/octet-stream";
3631 mime_enc = mimeGetContentEncoding(filename);
3632 break;
3633
3634 case 'A':
3635 mime_type = "text/plain";
3636 break;
3637
3638 default:
3639 mime_type = mimeGetContentType(filename);
3640 mime_enc = mimeGetContentEncoding(filename);
3641 break;
3642 }
3fdadc70 3643 }
62e76326 3644
cb69b4c7 3645 /* set standard stuff */
62e76326 3646
9fa2e208 3647 if (0 == getCurrentOffset()) {
690b22c6 3648 /* Full reply */
11992b6f 3649 reply->setHeaders(HTTP_OK, "Gatewaying", mime_type, theSize, mdtm, -2);
9fa2e208 3650 } else if (theSize < getCurrentOffset()) {
9e008dda
AJ
3651 /*
3652 * DPW 2007-05-04
3653 * offset should not be larger than theSize. We should
3654 * not be seeing this condition any more because we'll only
3655 * send REST if we know the theSize and if it is less than theSize.
3656 */
3657 debugs(0,DBG_CRITICAL,HERE << "Whoops! " <<
9fa2e208 3658 " current offset=" << getCurrentOffset() <<
9e008dda
AJ
3659 ", but theSize=" << theSize <<
3660 ". assuming full content response");
11992b6f 3661 reply->setHeaders(HTTP_OK, "Gatewaying", mime_type, theSize, mdtm, -2);
690b22c6 3662 } else {
62e76326 3663 /* Partial reply */
3664 HttpHdrRangeSpec range_spec;
9fa2e208
CT
3665 range_spec.offset = getCurrentOffset();
3666 range_spec.length = theSize - getCurrentOffset();
11992b6f 3667 reply->setHeaders(HTTP_PARTIAL_CONTENT, "Gatewaying", mime_type, theSize - getCurrentOffset(), mdtm, -2);
47f6e231 3668 httpHeaderAddContRange(&reply->header, range_spec, theSize);
cfbf5373 3669 }
62e76326 3670
cb69b4c7 3671 /* additional info */
3672 if (mime_enc)
a9925b40 3673 reply->header.putStr(HDR_CONTENT_ENCODING, mime_enc);
62e76326 3674
585ab260 3675 setVirginReply(reply);
3676 adaptOrFinalizeReply();
063a47b5 3677}
3678
3679void
3680FtpStateData::haveParsedReplyHeaders()
3681{
c1520b67
AJ
3682 ServerStateData::haveParsedReplyHeaders();
3683
063a47b5 3684 StoreEntry *e = entry;
62e76326 3685
3900307b 3686 e->timestampsSet();
62e76326 3687
6f0aab86 3688 if (flags.authenticated) {
62e76326 3689 /*
3690 * Authenticated requests can't be cached.
3691 */
5f33b71d 3692 e->release();
9fa2e208 3693 } else if (EBIT_TEST(e->flags, ENTRY_CACHABLE) && !getCurrentOffset()) {
d88e3c49 3694 e->setPublicKey();
c68e9c6b 3695 } else {
5f33b71d 3696 e->release();
c68e9c6b 3697 }
77a30ebb 3698}
bfcaf585 3699
6f0aab86 3700HttpReply *
3701FtpStateData::ftpAuthRequired(HttpRequest * request, const char *realm)
cb69b4c7 3702{
913524f0
AJ
3703 ErrorState err(ERR_CACHE_ACCESS_DENIED, HTTP_UNAUTHORIZED, request);
3704 HttpReply *newrep = err.BuildHttpReply();
9da6b594 3705#if HAVE_AUTH_MODULE_BASIC
63259c34 3706 /* add Authenticate header */
a9925b40 3707 newrep->header.putAuth("Basic", realm);
9da6b594 3708#endif
4a56ee8d 3709 return newrep;
cb69b4c7 3710}
8f872bb6 3711
cc192b50 3712/**
63be0a78 3713 \ingroup ServerProtocolFTPAPI
3714 \todo Should be a URL class API call.
3715 *
cc192b50 3716 * Construct an URI with leading / in PATH portion for use by CWD command
3717 * possibly others. FTP encodes absolute paths as beginning with '/'
3718 * after the initial URI path delimiter, which happens to be / itself.
3719 * This makes FTP absolute URI appear as: ftp:host:port//root/path
3720 * To encompass older software which compacts multiple // to / in transit
9e008dda 3721 * We use standard URI-encoding on the second / making it
cc192b50 3722 * ftp:host:port/%2froot/path AKA 'the FTP %2f hack'.
3723 */
3724const char *
3725ftpUrlWith2f(HttpRequest * request)
8f872bb6 3726{
cc192b50 3727 String newbuf = "%2f";
62e76326 3728
0c3d3f65 3729 if (request->protocol != AnyP::PROTO_FTP)
62e76326 3730 return NULL;
3731
d53b3f6d 3732 if ( request->urlpath[0]=='/' ) {
cc192b50 3733 newbuf.append(request->urlpath);
3734 request->urlpath.absorb(newbuf);
3735 safe_free(request->canonical);
d53b3f6d 3736 } else if ( !strncmp(request->urlpath.termedBuf(), "%2f", 3) ) {
826a1fed 3737 newbuf.append(request->urlpath.substr(1,request->urlpath.size()));
cc192b50 3738 request->urlpath.absorb(newbuf);
3739 safe_free(request->canonical);
8f872bb6 3740 }
62e76326 3741
cc192b50 3742 return urlCanonical(request);
8f872bb6 3743}
253caccb 3744
3745void
3746FtpStateData::printfReplyBody(const char *fmt, ...)
3747{
3748 va_list args;
3749 va_start (args, fmt);
3750 static char buf[4096];
3751 buf[0] = '\0';
3752 vsnprintf(buf, 4096, fmt, args);
3753 writeReplyBody(buf, strlen(buf));
3754}
3755
63be0a78 3756/**
253caccb 3757 * Call this when there is data from the origin server
3758 * which should be sent to either StoreEntry, or to ICAP...
3759 */
3760void
e053c141 3761FtpStateData::writeReplyBody(const char *dataToWrite, size_t dataLength)
253caccb 3762{
e053c141
FC
3763 debugs(9, 5, HERE << "writing " << dataLength << " bytes to the reply");
3764 addVirginReplyBody(dataToWrite, dataLength);
253caccb 3765}
3766
63be0a78 3767/**
3768 * called after we wrote the last byte of the request body
3769 */
16846d00 3770void
5f8252d2 3771FtpStateData::doneSendingRequestBody()
16846d00 3772{
39cb8c41 3773 ServerStateData::doneSendingRequestBody();
a689bd4e 3774 debugs(9,3, HERE);
f9ac241b 3775 dataComplete();
9e008dda
AJ
3776 /* NP: RFC 959 3.3. DATA CONNECTION MANAGEMENT
3777 * if transfer type is 'stream' call dataComplete()
3778 * otherwise leave open. (reschedule control channel read?)
3779 */
16846d00 3780}
3781
63be0a78 3782/**
3783 * A hack to ensure we do not double-complete on the forward entry.
3784 *
9e008dda 3785 \todo FtpStateData logic should probably be rewritten to avoid
63be0a78 3786 * double-completion or FwdState should be rewritten to allow it.
3787 */
16846d00 3788void
5f8252d2 3789FtpStateData::completeForwarding()
16846d00 3790{
5f8252d2 3791 if (fwd == NULL || flags.completed_forwarding) {
a689bd4e 3792 debugs(9, 3, HERE << "completeForwarding avoids " <<
6b679a01 3793 "double-complete on FD " << ctrl.conn->fd << ", Data FD " << data.conn->fd <<
9e008dda 3794 ", this " << this << ", fwd " << fwd);
5f8252d2 3795 return;
3796 }
16846d00 3797
5f8252d2 3798 flags.completed_forwarding = true;
3799 ServerStateData::completeForwarding();
16846d00 3800}
3801
63be0a78 3802/**
3803 * Close the FTP server connection(s). Used by serverComplete().
3804 */
253caccb 3805void
5f8252d2 3806FtpStateData::closeServer()
253caccb 3807{
6b679a01
AJ
3808 if (Comm::IsConnOpen(ctrl.conn)) {
3809 debugs(9,3, HERE << "closing FTP server FD " << ctrl.conn->fd << ", this " << this);
3810 fwd->unregister(ctrl.conn);
94b88585 3811 ctrl.close();
c0d75e74 3812 }
253caccb 3813
6200d3d6
AJ
3814 if (Comm::IsConnOpen(data.conn)) {
3815 debugs(9,3, HERE << "closing FTP data FD " << data.conn->fd << ", this " << this);
3816 data.close();
3817 }
3818
3819 debugs(9,3, HERE << "FTP ctrl and data connections closed. this " << this);
5f8252d2 3820}
253caccb 3821
63be0a78 3822/**
3823 * Did we close all FTP server connection(s)?
3824 *
6b679a01 3825 \retval true Both server control and data channels are closed. And not waiting for a new data connection to open.
63be0a78 3826 \retval false Either control channel or data is still active.
3827 */
5f8252d2 3828bool
3829FtpStateData::doneWithServer() const
3830{
6b679a01 3831 return !Comm::IsConnOpen(ctrl.conn) && !Comm::IsConnOpen(data.conn);
5f8252d2 3832}
c0d75e74 3833
63be0a78 3834/**
3835 * Have we lost the FTP server control channel?
3836 *
3837 \retval true The server control channel is available.
3838 \retval false The server control channel is not available.
3839 */
a11382aa 3840bool
4784c403 3841FtpStateData::haveControlChannel(const char *caller_name) const
a11382aa 3842{
9e008dda 3843 if (doneWithServer())
33ed5343 3844 return false;
a11382aa 3845
33ed5343 3846 /* doneWithServer() only checks BOTH channels are closed. */
6200d3d6 3847 if (!Comm::IsConnOpen(ctrl.conn)) {
a689bd4e 3848 debugs(9, DBG_IMPORTANT, "WARNING! FTP Server Control channel is closed, but Data channel still active.");
33ed5343 3849 debugs(9, 2, caller_name << ": attempted on a closed FTP channel.");
3850 return false;
3851 }
3852
3853 return true;
a11382aa 3854}
3855
63be0a78 3856/**
3857 * Quickly abort the transaction
3858 *
3859 \todo destruction should be sufficient as the destructor should cleanup,
3860 * including canceling close handlers
3861 */
5f8252d2 3862void
3863FtpStateData::abortTransaction(const char *reason)
3864{
a689bd4e 3865 debugs(9, 3, HERE << "aborting transaction for " << reason <<
8bbb16e3 3866 "; FD " << (ctrl.conn!=NULL?ctrl.conn->fd:-1) << ", Data FD " << (data.conn!=NULL?data.conn->fd:-1) << ", this " << this);
6b679a01
AJ
3867 if (Comm::IsConnOpen(ctrl.conn)) {
3868 ctrl.conn->close();
3e8c047e 3869 return;
3870 }
5a5b4ce1 3871
3e8c047e 3872 fwd->handleUnregisteredServerEnd();
79628299 3873 mustStop("FtpStateData::abortTransaction");
253caccb 3874}
94b88585
AR
3875
3876/// creates a data channel Comm close callback
3877AsyncCall::Pointer
3878FtpStateData::dataCloser()
3879{
3880 typedef CommCbMemFunT<FtpStateData, CommCloseCbParams> Dialer;
4299f876 3881 return JobCallback(9, 5, Dialer, this, FtpStateData::dataClosed);
94b88585
AR
3882}
3883
3884/// configures the channel with a descriptor and registers a close handler
3885void
6b679a01 3886FtpChannel::opened(const Comm::ConnectionPointer &newConn, const AsyncCall::Pointer &aCloser)
94b88585 3887{
6b679a01 3888 assert(!Comm::IsConnOpen(conn));
94b88585
AR
3889 assert(closer == NULL);
3890
6b679a01 3891 assert(Comm::IsConnOpen(newConn));
94b88585
AR
3892 assert(aCloser != NULL);
3893
6b679a01 3894 conn = newConn;
94b88585 3895 closer = aCloser;
6b679a01 3896 comm_add_close_handler(conn->fd, closer);
94b88585
AR
3897}
3898
3899/// planned close: removes the close handler and calls comm_close
3900void
3901FtpChannel::close()
3902{
04f55905 3903 // channels with active listeners will be closed when the listener handler dies.
8bbb16e3 3904 if (Comm::IsConnOpen(conn)) {
6b679a01 3905 comm_remove_close_handler(conn->fd, closer);
94b88585 3906 closer = NULL;
6b679a01 3907 conn->close(); // we do not expect to be called back
94b88585 3908 }
00406b24 3909 conn = NULL;
94b88585
AR
3910}
3911
94b88585
AR
3912void
3913FtpChannel::clear()
3914{
6b679a01 3915 conn = NULL;
94b88585
AR
3916 closer = NULL;
3917}