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