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