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