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