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