]> git.ipfire.org Git - thirdparty/squid.git/blame - src/clients/FtpGateway.cc
SourceFormat Enforcement
[thirdparty/squid.git] / src / clients / FtpGateway.cc
CommitLineData
30a4f2a8 1/*
ef57eb7b 2 * Copyright (C) 1996-2016 The Squid Software Foundation and contributors
e25c139f 3 *
bbc27441
AJ
4 * Squid software is distributed under GPLv2+ license and includes
5 * contributions from numerous individuals and organizations.
6 * Please see the COPYING and CONTRIBUTORS files for details.
30a4f2a8 7 */
019dd986 8
bbc27441
AJ
9/* DEBUG: section 09 File Transfer Protocol (FTP) */
10
582c2af2 11#include "squid.h"
ddf5aa2b 12#include "acl/FilledChecklist.h"
5517260a 13#include "clients/forward.h"
92ae4c86 14#include "clients/FtpClient.h"
04f55905 15#include "comm.h"
aed188fd 16#include "comm/ConnOpener.h"
7e66d5e2 17#include "comm/Read.h"
cbff89ba 18#include "comm/TcpAcceptor.h"
582c2af2 19#include "CommCalls.h"
27bc2077 20#include "compat/strtoll.h"
aa839030 21#include "errorpage.h"
c4ad1349 22#include "fd.h"
528b2c61 23#include "fde.h"
eb13c21e 24#include "FwdState.h"
25f98340 25#include "html_quote.h"
528b2c61 26#include "HttpHdrContRange.h"
27#include "HttpHeader.h"
582c2af2 28#include "HttpHeaderRange.h"
04f55905 29#include "HttpReply.h"
582c2af2 30#include "HttpRequest.h"
055421ee 31#include "ip/tools.h"
04f55905 32#include "MemBuf.h"
b65ce00c 33#include "mime.h"
056b24a1 34#include "rfc1738.h"
4d5904f7 35#include "SquidConfig.h"
056b24a1 36#include "SquidString.h"
04f55905 37#include "SquidTime.h"
e4f1fdae 38#include "StatCounters.h"
04f55905 39#include "Store.h"
4e540555 40#include "tools.h"
b1bd952a 41#include "URL.h"
ed6e9fb9 42#include "util.h"
04f55905
AJ
43#include "wordlist.h"
44
9a0a18de 45#if USE_DELAY_POOLS
b67e2c8c 46#include "DelayPools.h"
86a2f789 47#include "MemObject.h"
b67e2c8c 48#endif
253caccb 49
1a30fdf5 50#include <cerrno>
21d845b1 51
27c841f6
AR
52namespace Ftp
53{
5517260a 54
5517260a 55struct GatewayFlags {
4e2da309
AJ
56
57 /* passive mode */
58 bool pasv_supported; ///< PASV command is allowed
59 bool epsv_all_sent; ///< EPSV ALL has been used. Must abort on failures.
60 bool pasv_only;
6200d3d6 61 bool pasv_failed; // was FwdState::flags.ftp_pasv_failed
4e2da309
AJ
62
63 /* authentication */
64 bool authenticated; ///< authentication success
65 bool tried_auth_anonymous; ///< auth has tried to use anonymous credentials already.
66 bool tried_auth_nopass; ///< auth tried username with no password already.
67
68 /* other */
3977372c 69 bool isdir;
3977372c 70 bool skip_whitespace;
71 bool rest_supported;
3977372c 72 bool http_header_sent;
73 bool tried_nlst;
74 bool need_base_href;
75 bool dir_slash;
76 bool root_dir;
77 bool no_dotdot;
3977372c 78 bool binary;
79 bool try_slash_hack;
80 bool put;
81 bool put_mkdir;
82 bool listformat_unknown;
0477a072 83 bool listing;
5f8252d2 84 bool completed_forwarding;
e55f0142 85};
86
5517260a 87class Gateway;
5517260a 88typedef void (StateMethod)(Ftp::Gateway *);
6f0aab86 89
5517260a 90/// FTP Gateway: An FTP client that takes an HTTP request with an ftp:// URI,
e7ce227f 91/// converts it into one or more FTP commands, and then
5517260a
AR
92/// converts one or more FTP responses into the final HTTP response.
93class Gateway : public Ftp::Client
62e76326 94{
5c2f68b7
AJ
95 CBDATA_CLASS(Gateway);
96
0353e724 97public:
5517260a
AR
98 Gateway(FwdState *);
99 virtual ~Gateway();
77a30ebb 100 char user[MAX_URL];
101 char password[MAX_URL];
9bc73deb 102 int password_url;
f0afe435 103 char *reply_hdr;
f0afe435 104 int reply_hdr_state;
30abd221 105 String clean_url;
106 String title_url;
107 String base_href;
3fdadc70 108 int conn_att;
109 int login_att;
3fdadc70 110 time_t mdtm;
47f6e231 111 int64_t theSize;
3fdadc70 112 wordlist *pathcomps;
113 char *filepath;
5627a633 114 char *dirpath;
47f6e231 115 int64_t restart_offset;
3fdadc70 116 char *proxy_host;
117 size_t list_width;
0477a072 118 String cwd_message;
0f169992 119 char *old_filepath;
9e242e02 120 char typecode;
f53969cc 121 MemBuf listing; ///< FTP directory listing in HTML format.
62e76326 122
5517260a 123 GatewayFlags flags;
0353e724 124
6f0aab86 125public:
253caccb 126 // these should all be private
79628299 127 virtual void start();
434a79b0 128 virtual Http::StatusCode failedHttpStatus(err_type &error);
6f0aab86 129 int restartable();
130 void appendSuccessHeader();
5517260a 131 void hackShortcut(StateMethod *nextState);
6f0aab86 132 void unhack();
5f8252d2 133 void readStor();
f1a83a7d 134 void parseListing();
0477a072
AJ
135 MemBuf *htmlifyListEntry(const char *line);
136 void completedListing(void);
00406b24 137
00406b24 138 /// create a data channel acceptor and start listening.
434a79b0 139 void listenForDataChannel(const Comm::ConnectionPointer &conn);
00406b24 140
f1a83a7d 141 int checkAuth(const HttpHeader * req_hdr);
142 void checkUrlpath();
143 void buildTitleUrl();
47f6e231 144 void writeReplyBody(const char *, size_t len);
253caccb 145 void printfReplyBody(const char *fmt, ...);
5f8252d2 146 virtual void completeForwarding();
063a47b5 147 void processHeadResponse();
253caccb 148 void processReplyBody();
9fa2e208
CT
149 void setCurrentOffset(int64_t offset) { currentOffset = offset; }
150 int64_t getCurrentOffset() const { return currentOffset; }
6f0aab86 151
43446566 152 virtual void dataChannelConnected(const CommConnectCbParams &io);
6f0aab86 153 static PF ftpDataWrite;
434a79b0 154 virtual void timeout(const CommTimeoutCbParams &io);
dc56a9b1 155 void ftpAcceptDataConnection(const CommAcceptCbParams &io);
156
6f0aab86 157 static HttpReply *ftpAuthRequired(HttpRequest * request, const char *realm);
f381d699
AJ
158 const char *ftpRealm(void);
159 void loginFailed(void);
5f8252d2 160
063a47b5 161 virtual void haveParsedReplyHeaders();
162
aa190430 163 virtual bool haveControlChannel(const char *caller_name) const;
434a79b0
DK
164
165protected:
166 virtual void handleControlReply();
167 virtual void dataClosed(const CommCloseCbParams &io);
253caccb 168
5f8252d2 169private:
aea65fec 170 virtual bool mayReadVirginReplyBody() const;
5f8252d2 171 // BodyConsumer for HTTP: consume request body.
172 virtual void handleRequestBodyProducerAborted();
92d6986d
AJ
173
174 void loginParser(const SBuf &login, bool escaped);
0353e724 175};
176
5517260a
AR
177} // namespace Ftp
178
179typedef Ftp::StateMethod FTPSM; // to avoid lots of non-changes
180
181CBDATA_NAMESPACED_CLASS_INIT(Ftp, Gateway);
0353e724 182
9e008dda 183typedef struct {
3fdadc70 184 char type;
47f6e231 185 int64_t size;
3fdadc70 186 char *date;
187 char *name;
188 char *showname;
189 char *link;
2fadd50d 190} ftpListParts;
3fdadc70 191
5517260a 192#define CTRL_BUFLEN 1024
5517260a
AR
193static char cbuf[CTRL_BUFLEN];
194
7e3ce7b9 195/*
196 * State machine functions
969c39b9 197 * send == state transition
198 * read == wait for response, and select next state transition
dbfed404 199 * other == Transition logic
969c39b9 200 */
3fdadc70 201static FTPSM ftpReadWelcome;
969c39b9 202static FTPSM ftpSendUser;
3fdadc70 203static FTPSM ftpReadUser;
969c39b9 204static FTPSM ftpSendPass;
3fdadc70 205static FTPSM ftpReadPass;
969c39b9 206static FTPSM ftpSendType;
3fdadc70 207static FTPSM ftpReadType;
969c39b9 208static FTPSM ftpSendMdtm;
3fdadc70 209static FTPSM ftpReadMdtm;
969c39b9 210static FTPSM ftpSendSize;
3fdadc70 211static FTPSM ftpReadSize;
cc192b50 212static FTPSM ftpSendEPRT;
213static FTPSM ftpReadEPRT;
214static FTPSM ftpSendPORT;
215static FTPSM ftpReadPORT;
a689bd4e 216static FTPSM ftpSendPassive;
cc192b50 217static FTPSM ftpReadEPSV;
3fdadc70 218static FTPSM ftpReadPasv;
dbfed404 219static FTPSM ftpTraverseDirectory;
220static FTPSM ftpListDir;
221static FTPSM ftpGetFile;
969c39b9 222static FTPSM ftpSendCwd;
3fdadc70 223static FTPSM ftpReadCwd;
94439e4e 224static FTPSM ftpRestOrList;
969c39b9 225static FTPSM ftpSendList;
226static FTPSM ftpSendNlst;
3fdadc70 227static FTPSM ftpReadList;
969c39b9 228static FTPSM ftpSendRest;
3fdadc70 229static FTPSM ftpReadRest;
969c39b9 230static FTPSM ftpSendRetr;
3fdadc70 231static FTPSM ftpReadRetr;
232static FTPSM ftpReadTransferDone;
54220df8 233static FTPSM ftpSendStor;
234static FTPSM ftpReadStor;
94439e4e 235static FTPSM ftpWriteTransferDone;
54220df8 236static FTPSM ftpSendReply;
94439e4e 237static FTPSM ftpSendMkdir;
54220df8 238static FTPSM ftpReadMkdir;
94439e4e 239static FTPSM ftpFail;
240static FTPSM ftpSendQuit;
241static FTPSM ftpReadQuit;
a689bd4e 242
243/************************************************
244** Debugs Levels used here **
245*************************************************
f53969cc
SM
2460 CRITICAL Events
2471 IMPORTANT Events
248 Protocol and Transmission failures.
2492 FTP Protocol Chatter
2503 Logic Flows
2514 Data Parsing Flows
2525 Data Dumps
2537 ??
a689bd4e 254************************************************/
255
dbfed404 256/************************************************
257** State Machine Description (excluding hacks) **
258*************************************************
f53969cc 259From To
dbfed404 260---------------------------------------
f53969cc
SM
261Welcome User
262User Pass
263Pass Type
264Type TraverseDirectory / GetFile
265TraverseDirectory Cwd / GetFile / ListDir
266Cwd TraverseDirectory / Mkdir
267GetFile Mdtm
268Mdtm Size
269Size Epsv
270ListDir Epsv
271Epsv FileOrList
272FileOrList Rest / Retr / Nlst / List / Mkdir (PUT /xxx;type=d)
273Rest Retr
274Retr / Nlst / List DataRead* (on datachannel)
275DataRead* ReadTransferDone
276ReadTransferDone DataTransferDone
277Stor DataWrite* (on datachannel)
278DataWrite* RequestPutBody** (from client)
279RequestPutBody** DataWrite* / WriteTransferDone
280WriteTransferDone DataTransferDone
281DataTransferDone Quit
282Quit -
dbfed404 283************************************************/
3fdadc70 284
9e008dda 285FTPSM *FTP_SM_FUNCS[] = {
f53969cc
SM
286 ftpReadWelcome, /* BEGIN */
287 ftpReadUser, /* SENT_USER */
288 ftpReadPass, /* SENT_PASS */
289 ftpReadType, /* SENT_TYPE */
290 ftpReadMdtm, /* SENT_MDTM */
291 ftpReadSize, /* SENT_SIZE */
292 ftpReadEPRT, /* SENT_EPRT */
293 ftpReadPORT, /* SENT_PORT */
294 ftpReadEPSV, /* SENT_EPSV_ALL */
295 ftpReadEPSV, /* SENT_EPSV_1 */
296 ftpReadEPSV, /* SENT_EPSV_2 */
297 ftpReadPasv, /* SENT_PASV */
298 ftpReadCwd, /* SENT_CWD */
299 ftpReadList, /* SENT_LIST */
300 ftpReadList, /* SENT_NLST */
301 ftpReadRest, /* SENT_REST */
302 ftpReadRetr, /* SENT_RETR */
303 ftpReadStor, /* SENT_STOR */
304 ftpReadQuit, /* SENT_QUIT */
305 ftpReadTransferDone, /* READING_DATA (RETR,LIST,NLST) */
306 ftpWriteTransferDone, /* WRITING_DATA (STOR) */
307 ftpReadMkdir, /* SENT_MKDIR */
308 NULL, /* SENT_FEAT */
309 NULL, /* SENT_PWD */
310 NULL, /* SENT_CDUP*/
311 NULL, /* SENT_DATA_REQUEST */
312 NULL /* SENT_COMMAND */
9e008dda 313};
e381a13d 314
6e92b048 315/// handler called by Comm when FTP data channel is closed unexpectedly
9e008dda 316void
5517260a 317Ftp::Gateway::dataClosed(const CommCloseCbParams &io)
518a192e 318{
5517260a 319 Ftp::Client::dataClosed(io);
5a5b4ce1 320 failed(ERR_FTP_FAILURE, 0);
6b679a01 321 /* failed closes ctrl.conn and frees ftpState */
5a5b4ce1 322
6b679a01 323 /* NP: failure recovery may be possible when its only a data.conn failure.
6200d3d6 324 * if the ctrl.conn is still fine, we can send ABOR down it and retry.
5a5b4ce1
AJ
325 * Just need to watch out for wider Squid states like shutting down or reconfigure.
326 */
6f0aab86 327}
1be4874e 328
5517260a 329Ftp::Gateway::Gateway(FwdState *fwdState):
f53969cc
SM
330 AsyncJob("FtpStateData"),
331 Ftp::Client(fwdState),
332 password_url(0),
333 reply_hdr(NULL),
334 reply_hdr_state(0),
335 conn_att(0),
336 login_att(0),
337 mdtm(-1),
338 theSize(-1),
339 pathcomps(NULL),
340 filepath(NULL),
341 dirpath(NULL),
342 restart_offset(0),
343 proxy_host(NULL),
344 list_width(0),
345 old_filepath(NULL),
346 typecode('\0')
6f0aab86 347{
4579a6d0
AJ
348 debugs(9, 3, entry->url());
349
350 *user = 0;
351 *password = 0;
352 memset(&flags, 0, sizeof(flags));
6f0aab86 353
6200d3d6 354 if (Config.Ftp.passive && !flags.pasv_failed)
fe6b2914 355 flags.pasv_supported = 1;
6f0aab86 356
fe6b2914 357 flags.rest_supported = 1;
6f0aab86 358
c2a7cefd 359 if (request->method == Http::METHOD_PUT)
fe6b2914 360 flags.put = 1;
adaff124
DK
361
362 initReadBuf();
518a192e 363}
364
5517260a 365Ftp::Gateway::~Gateway()
ba718c8f 366{
4579a6d0 367 debugs(9, 3, entry->url());
62e76326 368
6b679a01 369 if (Comm::IsConnOpen(ctrl.conn)) {
e7ce227f
AR
370 debugs(9, DBG_IMPORTANT, "Internal bug: FTP Gateway left open " <<
371 "control channel " << ctrl.conn);
1ecaa0a0 372 }
62e76326 373
434a79b0
DK
374 if (reply_hdr) {
375 memFree(reply_hdr, MEM_8K_BUF);
376 reply_hdr = NULL;
968d691c 377 }
62e76326 378
6f0aab86 379 if (pathcomps)
380 wordlistDestroy(&pathcomps);
62e76326 381
0477a072 382 cwd_message.clean();
4579a6d0 383 xfree(old_filepath);
30abd221 384 title_url.clean();
30abd221 385 base_href.clean();
4579a6d0
AJ
386 xfree(filepath);
387 xfree(dirpath);
ba718c8f 388}
389
f381d699
AJ
390/**
391 * Parse a possible login username:password pair.
cd0b63ba 392 * Produces filled member variables user, password, password_url if anything found.
92d6986d
AJ
393 *
394 * \param login a decoded Basic authentication credential token or URI user-info token
395 * \param escaped whether to URL-decode the token after extracting user and password
f381d699 396 */
6f0aab86 397void
92d6986d 398Ftp::Gateway::loginParser(const SBuf &login, bool escaped)
090089c4 399{
92d6986d
AJ
400 debugs(9, 4, "login=" << login << ", escaped=" << escaped);
401 debugs(9, 9, "IN : login=" << login << ", escaped=" << escaped << ", user=" << user << ", password=" << password);
62e76326 402
92d6986d
AJ
403 if (login.isEmpty())
404 return;
405
406 const SBuf::size_type colonPos = login.find(':');
407
408 /* If there was a username part with at least one character use it.
409 * Ignore 0-length username portion, retain what we have already.
410 */
411 if (colonPos == SBuf::npos || colonPos > 0) {
412 const SBuf userName = login.substr(0, colonPos);
413 SBuf::size_type upto = userName.copy(user, sizeof(user)-1);
414 user[upto]='\0';
415 debugs(9, 9, "found user=" << userName << ' ' <<
416 (upto != userName.length() ? ", truncated-to=" : ", length=") << upto <<
417 ", escaped=" << escaped);
f381d699
AJ
418 if (escaped)
419 rfc1738_unescape(user);
92d6986d
AJ
420 debugs(9, 9, "found user=" << user << " (" << strlen(user) << ") unescaped.");
421 }
422
423 /* If there was a password part.
424 * For 0-length password clobber what we have already, this means explicitly none
425 */
426 if (colonPos != SBuf::npos) {
427 const SBuf pass = login.substr(colonPos+1, SBuf::npos);
428 SBuf::size_type upto = pass.copy(password, sizeof(password)-1);
429 password[upto]='\0';
430 debugs(9, 9, "found password=" << pass << " " <<
431 (upto != pass.length() ? ", truncated-to=" : ", length=") << upto <<
432 ", escaped=" << escaped);
433 if (escaped) {
434 rfc1738_unescape(password);
435 password_url = 1;
436 }
437 debugs(9, 9, "found password=" << password << " (" << strlen(password) << ") unescaped.");
f381d699 438 }
62e76326 439
92d6986d 440 debugs(9, 9, "OUT: login=" << login << ", escaped=" << escaped << ", user=" << user << ", password=" << password);
090089c4 441}
442
00406b24 443void
5517260a 444Ftp::Gateway::listenForDataChannel(const Comm::ConnectionPointer &conn)
00406b24 445{
8bbb16e3 446 assert(!Comm::IsConnOpen(data.conn));
00406b24 447
5517260a 448 typedef CommCbMemFunT<Gateway, CommAcceptCbParams> AcceptDialer;
00406b24 449 typedef AsyncCallT<AcceptDialer> AcceptCall;
5517260a 450 RefCount<AcceptCall> call = static_cast<AcceptCall*>(JobCallback(11, 5, AcceptDialer, this, Ftp::Gateway::ftpAcceptDataConnection));
00406b24 451 Subscription::Pointer sub = new CallSubscription<AcceptCall>(call);
434a79b0 452 const char *note = entry->url();
cbff89ba
AJ
453
454 /* open the conn if its not already open */
8bbb16e3
AJ
455 if (!Comm::IsConnOpen(conn)) {
456 conn->fd = comm_open_listener(SOCK_STREAM, IPPROTO_TCP, conn->local, conn->flags, note);
457 if (!Comm::IsConnOpen(conn)) {
458 debugs(5, DBG_CRITICAL, HERE << "comm_open_listener failed:" << conn->local << " error: " << errno);
cbff89ba
AJ
459 return;
460 }
8bbb16e3 461 debugs(9, 3, HERE << "Unconnected data socket created on " << conn);
cbff89ba
AJ
462 }
463
c6f168c1
CT
464 conn->tos = ctrl.conn->tos;
465 conn->nfmark = ctrl.conn->nfmark;
466
8bbb16e3
AJ
467 assert(Comm::IsConnOpen(conn));
468 AsyncJob::Start(new Comm::TcpAcceptor(conn, note, sub));
cbff89ba
AJ
469
470 // Ensure we have a copy of the FD opened for listening and a close handler on it.
8bbb16e3 471 data.opened(conn, dataCloser());
cbff89ba 472 switchTimeoutToDataChannel();
00406b24
AJ
473}
474
6f0aab86 475void
5517260a 476Ftp::Gateway::timeout(const CommTimeoutCbParams &io)
090089c4 477{
05cb9a45
AJ
478 if (SENT_PASV == state) {
479 /* stupid ftp.netscape.com, of FTP server behind stupid firewall rules */
d890b809 480 flags.pasv_supported = false;
e7ce227f 481 debugs(9, DBG_IMPORTANT, "FTP Gateway timeout in SENT_PASV state");
05cb9a45
AJ
482
483 // cancel the data connection setup.
484 if (data.opener != NULL) {
485 data.opener->cancel("timeout");
486 data.opener = NULL;
487 }
488 data.close();
7e3ce7b9 489 }
62e76326 490
5517260a 491 Ftp::Client::timeout(io);
090089c4 492}
493
9e008dda
AJ
494static const char *Month[] = {
495 "Jan", "Feb", "Mar", "Apr", "May", "Jun",
496 "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"
497};
3fdadc70 498
499static int
500is_month(const char *buf)
501{
502 int i;
62e76326 503
95dc7ff4 504 for (i = 0; i < 12; ++i)
62e76326 505 if (!strcasecmp(buf, Month[i]))
506 return 1;
507
3fdadc70 508 return 0;
509}
510
3fdadc70 511static void
512ftpListPartsFree(ftpListParts ** parts)
513{
514 safe_free((*parts)->date);
515 safe_free((*parts)->name);
516 safe_free((*parts)->showname);
517 safe_free((*parts)->link);
518 safe_free(*parts);
519}
520
521#define MAX_TOKENS 64
522
523static ftpListParts *
5517260a 524ftpListParseParts(const char *buf, struct Ftp::GatewayFlags flags)
3fdadc70 525{
526 ftpListParts *p = NULL;
527 char *t = NULL;
528 const char *ct = NULL;
529 char *tokens[MAX_TOKENS];
530 int i;
531 int n_tokens;
748f6a15 532 static char tbuf[128];
3fdadc70 533 char *xbuf = NULL;
748f6a15 534 static int scan_ftp_initialized = 0;
535 static regex_t scan_ftp_integer;
536 static regex_t scan_ftp_time;
537 static regex_t scan_ftp_dostime;
538 static regex_t scan_ftp_dosdate;
539
9e008dda 540 if (!scan_ftp_initialized) {
62e76326 541 scan_ftp_initialized = 1;
542 regcomp(&scan_ftp_integer, "^[0123456789]+$", REG_EXTENDED | REG_NOSUB);
543 regcomp(&scan_ftp_time, "^[0123456789:]+$", REG_EXTENDED | REG_NOSUB);
544 regcomp(&scan_ftp_dosdate, "^[0123456789]+-[0123456789]+-[0123456789]+$", REG_EXTENDED | REG_NOSUB);
545 regcomp(&scan_ftp_dostime, "^[0123456789]+:[0123456789]+[AP]M$", REG_EXTENDED | REG_NOSUB | REG_ICASE);
748f6a15 546 }
62e76326 547
3fdadc70 548 if (buf == NULL)
62e76326 549 return NULL;
550
3fdadc70 551 if (*buf == '\0')
62e76326 552 return NULL;
553
e6ccf245 554 p = (ftpListParts *)xcalloc(1, sizeof(ftpListParts));
62e76326 555
3fdadc70 556 n_tokens = 0;
62e76326 557
748f6a15 558 memset(tokens, 0, sizeof(tokens));
62e76326 559
3fdadc70 560 xbuf = xstrdup(buf);
62e76326 561
9e008dda 562 if (flags.tried_nlst) {
62e76326 563 /* Machine readable format, one name per line */
564 p->name = xbuf;
565 p->type = '\0';
566 return p;
dbfed404 567 }
62e76326 568
a38ec4b1
FC
569 for (t = strtok(xbuf, w_space); t && n_tokens < MAX_TOKENS; t = strtok(NULL, w_space)) {
570 tokens[n_tokens] = xstrdup(t);
571 ++n_tokens;
572 }
62e76326 573
3fdadc70 574 xfree(xbuf);
62e76326 575
3fdadc70 576 /* locate the Month field */
95dc7ff4 577 for (i = 3; i < n_tokens - 2; ++i) {
62e76326 578 char *size = tokens[i - 1];
579 char *month = tokens[i];
580 char *day = tokens[i + 1];
581 char *year = tokens[i + 2];
582
583 if (!is_month(month))
584 continue;
585
586 if (regexec(&scan_ftp_integer, size, 0, NULL, 0) != 0)
587 continue;
588
589 if (regexec(&scan_ftp_integer, day, 0, NULL, 0) != 0)
590 continue;
591
f53969cc 592 if (regexec(&scan_ftp_time, year, 0, NULL, 0) != 0) /* Yr | hh:mm */
62e76326 593 continue;
594
595 snprintf(tbuf, 128, "%s %2s %5s",
596 month, day, year);
597
598 if (!strstr(buf, tbuf))
599 snprintf(tbuf, 128, "%s %2s %-5s",
600 month, day, year);
601
65bfb9ab 602 char const *copyFrom = NULL;
603
604 if ((copyFrom = strstr(buf, tbuf))) {
62e76326 605 p->type = *tokens[0];
47f6e231 606 p->size = strtoll(size, NULL, 10);
62e76326 607 p->date = xstrdup(tbuf);
608
609 if (flags.skip_whitespace) {
65bfb9ab 610 copyFrom += strlen(tbuf);
62e76326 611
65bfb9ab 612 while (strchr(w_space, *copyFrom))
95dc7ff4 613 ++copyFrom;
62e76326 614 } else {
615 /* XXX assumes a single space between date and filename
616 * suggested by: Nathan.Bailey@cc.monash.edu.au and
617 * Mike Battersby <mike@starbug.bofh.asn.au> */
65bfb9ab 618 copyFrom += strlen(tbuf) + 1;
62e76326 619 }
620
65bfb9ab 621 p->name = xstrdup(copyFrom);
62e76326 622
38ebaefc 623 if (p->type == 'l' && (t = strstr(p->name, " -> "))) {
62e76326 624 *t = '\0';
625 p->link = xstrdup(t + 4);
626 }
627
628 goto found;
629 }
630
631 break;
3fdadc70 632 }
62e76326 633
748f6a15 634 /* try it as a DOS listing, 04-05-70 09:33PM ... */
635 if (n_tokens > 3 &&
62e76326 636 regexec(&scan_ftp_dosdate, tokens[0], 0, NULL, 0) == 0 &&
9e008dda 637 regexec(&scan_ftp_dostime, tokens[1], 0, NULL, 0) == 0) {
62e76326 638 if (!strcasecmp(tokens[2], "<dir>")) {
639 p->type = 'd';
640 } else {
641 p->type = '-';
47f6e231 642 p->size = strtoll(tokens[2], NULL, 10);
62e76326 643 }
644
645 snprintf(tbuf, 128, "%s %s", tokens[0], tokens[1]);
646 p->date = xstrdup(tbuf);
647
648 if (p->type == 'd') {
649 /* Directory.. name begins with first printable after <dir> */
650 ct = strstr(buf, tokens[2]);
651 ct += strlen(tokens[2]);
652
653 while (xisspace(*ct))
95dc7ff4 654 ++ct;
62e76326 655
656 if (!*ct)
657 ct = NULL;
658 } else {
659 /* A file. Name begins after size, with a space in between */
660 snprintf(tbuf, 128, " %s %s", tokens[2], tokens[3]);
661 ct = strstr(buf, tbuf);
662
663 if (ct) {
664 ct += strlen(tokens[2]) + 2;
665 }
666 }
667
668 p->name = xstrdup(ct ? ct : tokens[3]);
669 goto found;
3fdadc70 670 }
62e76326 671
3fdadc70 672 /* Try EPLF format; carson@lehman.com */
9e008dda 673 if (buf[0] == '+') {
62e76326 674 ct = buf + 1;
675 p->type = 0;
676
677 while (ct && *ct) {
d5f8d05f 678 time_t tm;
3f9d0b2d 679 int l = strcspn(ct, ",");
62e76326 680 char *tmp;
681
682 if (l < 1)
683 goto blank;
684
685 switch (*ct) {
686
687 case '\t':
688 p->name = xstrndup(ct + 1, l + 1);
689 break;
690
691 case 's':
692 p->size = atoi(ct + 1);
693 break;
694
695 case 'm':
d5f8d05f 696 tm = (time_t) strtol(ct + 1, &tmp, 0);
62e76326 697
3f9d0b2d 698 if (tmp != ct + 1)
f53969cc 699 break; /* not a valid integer */
62e76326 700
d5f8d05f 701 p->date = xstrdup(ctime(&tm));
62e76326 702
703 *(strstr(p->date, "\n")) = '\0';
704
705 break;
706
707 case '/':
708 p->type = 'd';
709
710 break;
711
712 case 'r':
713 p->type = '-';
714
715 break;
716
717 case 'i':
718 break;
719
720 default:
721 break;
722 }
723
724blank:
725 ct = strstr(ct, ",");
726
727 if (ct) {
95dc7ff4 728 ++ct;
62e76326 729 }
730 }
731
732 if (p->type == 0) {
733 p->type = '-';
734 }
735
736 if (p->name)
737 goto found;
738 else
739 safe_free(p->date);
740 }
741
742found:
743
95dc7ff4 744 for (i = 0; i < n_tokens; ++i)
62e76326 745 xfree(tokens[i]);
746
748f6a15 747 if (!p->name)
f53969cc 748 ftpListPartsFree(&p); /* cleanup */
62e76326 749
3fdadc70 750 return p;
751}
752
0477a072 753MemBuf *
5517260a 754Ftp::Gateway::htmlifyListEntry(const char *line)
3fdadc70 755{
0477a072
AJ
756 char icon[2048];
757 char href[2048 + 40];
758 char text[ 2048];
759 char size[ 2048];
760 char chdir[ 2048 + 40];
761 char view[ 2048 + 40];
762 char download[ 2048 + 40];
763 char link[ 2048 + 40];
764 MemBuf *html;
765 char prefix[2048];
3fdadc70 766 ftpListParts *parts;
0477a072 767 *icon = *href = *text = *size = *chdir = *view = *download = *link = '\0';
62e76326 768
0477a072 769 debugs(9, 7, HERE << " line ={" << line << "}");
62e76326 770
0477a072
AJ
771 if (strlen(line) > 1024) {
772 html = new MemBuf();
773 html->init();
4391cd15 774 html->appendf("<tr><td colspan=\"5\">%s</td></tr>\n", line);
62e76326 775 return html;
3fdadc70 776 }
62e76326 777
45289935 778 if (flags.dir_slash && dirpath && typecode != 'D')
779 snprintf(prefix, 2048, "%s/", rfc1738_escape_part(dirpath));
5627a633 780 else
781 prefix[0] = '\0';
782
6f0aab86 783 if ((parts = ftpListParseParts(line, flags)) == NULL) {
62e76326 784 const char *p;
0477a072
AJ
785
786 html = new MemBuf();
787 html->init();
4391cd15 788 html->appendf("<tr class=\"entry\"><td colspan=\"5\">%s</td></tr>\n", line);
62e76326 789
95dc7ff4 790 for (p = line; *p && xisspace(*p); ++p);
62e76326 791 if (*p && !xisspace(*p))
6f0aab86 792 flags.listformat_unknown = 1;
62e76326 793
794 return html;
13e80e5b 795 }
62e76326 796
3fdadc70 797 if (!strcmp(parts->name, ".") || !strcmp(parts->name, "..")) {
62e76326 798 ftpListPartsFree(&parts);
0477a072 799 return NULL;
3fdadc70 800 }
62e76326 801
3fdadc70 802 parts->size += 1023;
803 parts->size >>= 10;
804 parts->showname = xstrdup(parts->name);
62e76326 805
2a1ca944 806 /* {icon} {text} . . . {date}{size}{chdir}{view}{download}{link}\n */
9bc73deb 807 xstrncpy(href, rfc1738_escape_part(parts->name), 2048);
62e76326 808
2a1ca944 809 xstrncpy(text, parts->showname, 2048);
62e76326 810
3fdadc70 811 switch (parts->type) {
62e76326 812
3fdadc70 813 case 'd':
0477a072 814 snprintf(icon, 2048, "<img border=\"0\" src=\"%s\" alt=\"%-6s\">",
62e76326 815 mimeGetIconURL("internal-dir"),
816 "[DIR]");
f53969cc 817 strcat(href, "/"); /* margin is allocated above */
62e76326 818 break;
819
3fdadc70 820 case 'l':
0477a072 821 snprintf(icon, 2048, "<img border=\"0\" src=\"%s\" alt=\"%-6s\">",
62e76326 822 mimeGetIconURL("internal-link"),
823 "[LINK]");
824 /* sometimes there is an 'l' flag, but no "->" link */
825
826 if (parts->link) {
827 char *link2 = xstrdup(html_quote(rfc1738_escape(parts->link)));
0477a072 828 snprintf(link, 2048, " -&gt; <a href=\"%s%s\">%s</a>",
5627a633 829 *link2 != '/' ? prefix : "", link2,
62e76326 830 html_quote(parts->link));
831 safe_free(link2);
832 }
833
834 break;
835
dbfed404 836 case '\0':
0477a072 837 snprintf(icon, 2048, "<img border=\"0\" src=\"%s\" alt=\"%-6s\">",
62e76326 838 mimeGetIconURL(parts->name),
839 "[UNKNOWN]");
0477a072
AJ
840 snprintf(chdir, 2048, "<a href=\"%s/;type=d\"><img border=\"0\" src=\"%s\" "
841 "alt=\"[DIR]\"></a>",
62e76326 842 rfc1738_escape_part(parts->name),
843 mimeGetIconURL("internal-dir"));
844 break;
845
3fdadc70 846 case '-':
62e76326 847
3fdadc70 848 default:
0477a072 849 snprintf(icon, 2048, "<img border=\"0\" src=\"%s\" alt=\"%-6s\">",
62e76326 850 mimeGetIconURL(parts->name),
851 "[FILE]");
c91ca3ce 852 snprintf(size, 2048, " %6" PRId64 "k", parts->size);
62e76326 853 break;
3fdadc70 854 }
62e76326 855
2a1ca944 856 if (parts->type != 'd') {
62e76326 857 if (mimeGetViewOption(parts->name)) {
0477a072
AJ
858 snprintf(view, 2048, "<a href=\"%s%s;type=a\"><img border=\"0\" src=\"%s\" "
859 "alt=\"[VIEW]\"></a>",
5627a633 860 prefix, href, mimeGetIconURL("internal-view"));
62e76326 861 }
862
863 if (mimeGetDownloadOption(parts->name)) {
0477a072
AJ
864 snprintf(download, 2048, "<a href=\"%s%s;type=i\"><img border=\"0\" src=\"%s\" "
865 "alt=\"[DOWNLOAD]\"></a>",
5627a633 866 prefix, href, mimeGetIconURL("internal-download"));
62e76326 867 }
2a1ca944 868 }
62e76326 869
0477a072
AJ
870 /* construct the table row from parts. */
871 html = new MemBuf();
872 html->init();
4391cd15 873 html->appendf("<tr class=\"entry\">"
f680026f
SM
874 "<td class=\"icon\"><a href=\"%s%s\">%s</a></td>"
875 "<td class=\"filename\"><a href=\"%s%s\">%s</a></td>"
876 "<td class=\"date\">%s</td>"
877 "<td class=\"size\">%s</td>"
878 "<td class=\"actions\">%s%s%s%s</td>"
879 "</tr>\n",
880 prefix, href, icon,
881 prefix, href, html_quote(text),
882 parts->date,
883 size,
884 chdir, view, download, link);
62e76326 885
3fdadc70 886 ftpListPartsFree(&parts);
3fdadc70 887 return html;
888}
889
f1a83a7d 890void
5517260a 891Ftp::Gateway::parseListing()
3fdadc70 892{
253caccb 893 char *buf = data.readBuf->content();
f53969cc 894 char *sbuf; /* NULL-terminated copy of termedBuf */
b5639035 895 char *end;
896 char *line;
3fdadc70 897 char *s;
0477a072 898 MemBuf *t;
3fdadc70 899 size_t linelen;
b5639035 900 size_t usable;
253caccb 901 size_t len = data.readBuf->contentSize();
5f8252d2 902
903 if (!len) {
0477a072 904 debugs(9, 3, HERE << "no content to parse for " << entry->url() );
5f8252d2 905 return;
906 }
907
7131112f 908 /*
909 * We need a NULL-terminated buffer for scanning, ick
910 */
e6ccf245 911 sbuf = (char *)xmalloc(len + 1);
7131112f 912 xstrncpy(sbuf, buf, len + 1);
913 end = sbuf + len - 1;
62e76326 914
7131112f 915 while (*end != '\r' && *end != '\n' && end > sbuf)
5e263176 916 --end;
62e76326 917
7131112f 918 usable = end - sbuf;
62e76326 919
cbff89ba 920 debugs(9, 3, HERE << "usable = " << usable << " of " << len << " bytes.");
62e76326 921
b5639035 922 if (usable == 0) {
cbff89ba
AJ
923 if (buf[0] == '\0' && len == 1) {
924 debugs(9, 3, HERE << "NIL ends data from " << entry->url() << " transfer problem?");
925 data.readBuf->consume(len);
926 } else {
927 debugs(9, 3, HERE << "didn't find end for " << entry->url());
928 debugs(9, 3, HERE << "buffer remains (" << len << " bytes) '" << rfc1738_do_escape(buf,0) << "'");
929 }
62e76326 930 xfree(sbuf);
931 return;
3fdadc70 932 }
62e76326 933
a689bd4e 934 debugs(9, 3, HERE << (unsigned long int)len << " bytes to play with");
bf8fe701 935
e6ccf245 936 line = (char *)memAllocate(MEM_4K_BUF);
95dc7ff4 937 ++end;
362be274 938 s = sbuf;
43446566 939 s += strspn(s, crlf);
62e76326 940
43446566 941 for (; s < end; s += strcspn(s, crlf), s += strspn(s, crlf)) {
a689bd4e 942 debugs(9, 7, HERE << "s = {" << s << "}");
43446566 943 linelen = strcspn(s, crlf) + 1;
62e76326 944
945 if (linelen < 2)
946 break;
947
948 if (linelen > 4096)
949 linelen = 4096;
950
951 xstrncpy(line, s, linelen);
952
a689bd4e 953 debugs(9, 7, HERE << "{" << line << "}");
62e76326 954
955 if (!strncmp(line, "total", 5))
956 continue;
957
f1a83a7d 958 t = htmlifyListEntry(line);
62e76326 959
05320519 960 if ( t != NULL) {
0477a072
AJ
961 debugs(9, 7, HERE << "listing append: t = {" << t->contentSize() << ", '" << t->content() << "'}");
962 listing.append(t->content(), t->contentSize());
6175a21f 963 delete t;
0477a072 964 }
b5639035 965 }
62e76326 966
0477a072 967 debugs(9, 7, HERE << "Done.");
253caccb 968 data.readBuf->consume(usable);
db1cd23c 969 memFree(line, MEM_4K_BUF);
4a2635aa 970 xfree(sbuf);
3fdadc70 971}
090089c4 972
253caccb 973void
5517260a 974Ftp::Gateway::processReplyBody()
253caccb 975{
e7ce227f 976 debugs(9, 3, status());
063a47b5 977
c2a7cefd 978 if (request->method == Http::METHOD_HEAD && (flags.isdir || theSize != -1)) {
063a47b5 979 serverComplete();
980 return;
981 }
982
0477a072
AJ
983 /* Directory listings are special. They write ther own headers via the error objects */
984 if (!flags.http_header_sent && data.readBuf->contentSize() >= 0 && !flags.isdir)
253caccb 985 appendSuccessHeader();
62e76326 986
35f26998 987 if (EBIT_TEST(entry->flags, ENTRY_ABORTED)) {
9e008dda
AJ
988 /*
989 * probably was aborted because content length exceeds one
990 * of the maximum size limits.
991 */
35f26998 992 abortTransaction("entry aborted after calling appendSuccessHeader()");
993 return;
994 }
995
a83c6ed6 996#if USE_ADAPTATION
62e76326 997
a83c6ed6 998 if (adaptationAccessCheckPending) {
e7ce227f 999 debugs(9, 3, "returning from Ftp::Gateway::processReplyBody due to adaptationAccessCheckPending");
253caccb 1000 return;
1001 }
62e76326 1002
c4b7a5a9 1003#endif
62e76326 1004
253caccb 1005 if (flags.isdir) {
0477a072
AJ
1006 if (!flags.listing) {
1007 flags.listing = 1;
1008 listing.reset();
1009 }
253caccb 1010 parseListing();
0477a072
AJ
1011 maybeReadVirginBody();
1012 return;
e1381638
AJ
1013 } else if (const int csize = data.readBuf->contentSize()) {
1014 writeReplyBody(data.readBuf->content(), csize);
1015 debugs(9, 5, HERE << "consuming " << csize << " bytes of readBuf");
1016 data.readBuf->consume(csize);
1017 }
253caccb 1018
3900307b 1019 entry->flush();
253caccb 1020
5f8252d2 1021 maybeReadVirginBody();
090089c4 1022}
1023
63be0a78 1024/**
f381d699
AJ
1025 * Locates the FTP user:password login.
1026 *
1027 * Highest to lowest priority:
1028 * - Checks URL (ftp://user:pass@domain)
1029 * - Authorization: Basic header
1030 * - squid.conf anonymous-FTP settings (default: anonymous:Squid@).
1031 *
1032 * Special Case: A username-only may be provided in the URL and password in the HTTP headers.
1033 *
4e2da309
AJ
1034 * TODO: we might be able to do something about locating username from other sources:
1035 * ie, external ACL user=* tag or ident lookup
1036 *
f53969cc
SM
1037 \retval 1 if we have everything needed to complete this request.
1038 \retval 0 if something is missing.
429fdbec 1039 */
f1a83a7d 1040int
5517260a 1041Ftp::Gateway::checkAuth(const HttpHeader * req_hdr)
429fdbec 1042{
f381d699
AJ
1043 /* default username */
1044 xstrncpy(user, "anonymous", MAX_URL);
62e76326 1045
9da6b594 1046#if HAVE_AUTH_MODULE_BASIC
f381d699 1047 /* Check HTTP Authorization: headers (better than defaults, but less than URL) */
789217a2 1048 const SBuf auth(req_hdr->getAuth(Http::HdrType::AUTHORIZATION, "Basic"));
92d6986d 1049 if (!auth.isEmpty()) {
f381d699 1050 flags.authenticated = 1;
92d6986d 1051 loginParser(auth, false);
f381d699
AJ
1052 }
1053 /* we fail with authorization-required error later IFF the FTP server requests it */
9da6b594 1054#endif
62e76326 1055
f381d699 1056 /* Test URL login syntax. Overrides any headers received. */
92d6986d 1057 loginParser(request->url.userInfo(), true);
62e76326 1058
f381d699
AJ
1059 /* name is missing. thats fatal. */
1060 if (!user[0])
1061 fatal("FTP login parsing destroyed username info");
62e76326 1062
f381d699
AJ
1063 /* name + password == success */
1064 if (password[0])
1065 return 1;
62e76326 1066
f381d699
AJ
1067 /* Setup default FTP password settings */
1068 /* this has to be done last so that we can have a no-password case above. */
1069 if (!password[0]) {
4e2da309 1070 if (strcmp(user, "anonymous") == 0 && !flags.tried_auth_anonymous) {
f381d699 1071 xstrncpy(password, Config.Ftp.anon_user, MAX_URL);
4e2da309
AJ
1072 flags.tried_auth_anonymous=1;
1073 return 1;
e1381638 1074 } else if (!flags.tried_auth_nopass) {
f381d699 1075 xstrncpy(password, null_string, MAX_URL);
4e2da309
AJ
1076 flags.tried_auth_nopass=1;
1077 return 1;
1078 }
429fdbec 1079 }
62e76326 1080
f53969cc 1081 return 0; /* different username */
429fdbec 1082}
1083
f1a83a7d 1084void
5517260a 1085Ftp::Gateway::checkUrlpath()
13e80e5b 1086{
51b5dcf5
AJ
1087 static SBuf str_type_eq("type=");
1088 auto t = request->url.path().rfind(';');
62e76326 1089
51b5dcf5
AJ
1090 if (t != SBuf::npos) {
1091 auto filenameEnd = t-1;
1092 if (request->url.path().substr(++t).cmp(str_type_eq, str_type_eq.length()) == 0) {
1093 t += str_type_eq.length();
1094 typecode = (char)xtoupper(request->url.path()[t]);
1095 request->url.path(request->url.path().substr(0,filenameEnd));
62e76326 1096 }
dbfed404 1097 }
62e76326 1098
51b5dcf5 1099 int l = request->url.path().length();
13e80e5b 1100 /* check for null path */
62e76326 1101
02922e76 1102 if (!l) {
f1a83a7d 1103 flags.isdir = 1;
1104 flags.root_dir = 1;
f53969cc 1105 flags.need_base_href = 1; /* Work around broken browsers */
51b5dcf5 1106 } else if (!request->url.path().cmp("/%2f/")) {
62e76326 1107 /* UNIX root directory */
f1a83a7d 1108 flags.isdir = 1;
1109 flags.root_dir = 1;
51b5dcf5 1110 } else if ((l >= 1) && (request->url.path()[l-1] == '/')) {
62e76326 1111 /* Directory URL, ending in / */
f1a83a7d 1112 flags.isdir = 1;
62e76326 1113
1114 if (l == 1)
f1a83a7d 1115 flags.root_dir = 1;
d5f80edc 1116 } else {
f1a83a7d 1117 flags.dir_slash = 1;
13e80e5b 1118 }
1119}
1120
f1a83a7d 1121void
5517260a 1122Ftp::Gateway::buildTitleUrl()
3fdadc70 1123{
f1a83a7d 1124 title_url = "ftp://";
62e76326 1125
f1a83a7d 1126 if (strcmp(user, "anonymous")) {
1127 title_url.append(user);
1128 title_url.append("@");
06a5d871 1129 }
62e76326 1130
5c51bffb 1131 SBuf authority = request->url.authority(request->url.getScheme() != AnyP::PROTO_FTP);
62e76326 1132
5c51bffb 1133 title_url.append(authority.rawContent(), authority.length());
51b5dcf5 1134 title_url.append(request->url.path().rawContent(), request->url.path().length());
06a5d871 1135
f1a83a7d 1136 base_href = "ftp://";
62e76326 1137
f1a83a7d 1138 if (strcmp(user, "anonymous") != 0) {
1139 base_href.append(rfc1738_escape_part(user));
62e76326 1140
f1a83a7d 1141 if (password_url) {
5c51bffb 1142 base_href.append(":");
f1a83a7d 1143 base_href.append(rfc1738_escape_part(password));
62e76326 1144 }
1145
f1a83a7d 1146 base_href.append("@");
06a5d871 1147 }
62e76326 1148
5c51bffb 1149 base_href.append(authority.rawContent(), authority.length());
51b5dcf5 1150 base_href.append(request->url.path().rawContent(), request->url.path().length());
f1a83a7d 1151 base_href.append("/");
3fdadc70 1152}
1153
770f051d 1154void
5517260a 1155Ftp::Gateway::start()
6f0aab86 1156{
f1a83a7d 1157 if (!checkAuth(&request->header)) {
62e76326 1158 /* create appropriate reply */
f381d699 1159 HttpReply *reply = ftpAuthRequired(request, ftpRealm());
db237875 1160 entry->replaceHttpReply(reply);
5f8252d2 1161 serverComplete();
62e76326 1162 return;
e381a13d 1163 }
62e76326 1164
f1a83a7d 1165 checkUrlpath();
1166 buildTitleUrl();
51b5dcf5
AJ
1167 debugs(9, 5, "FD " << ctrl.conn->fd << " : host=" << request->url.host() <<
1168 ", path=" << request->url.path() << ", user=" << user << ", passwd=" << password);
6f0aab86 1169 state = BEGIN;
5517260a 1170 Ftp::Client::start();
dad0fe12 1171}
1172
434a79b0 1173/* ====================================================================== */
4f310655 1174
6f0aab86 1175void
5517260a 1176Ftp::Gateway::handleControlReply()
4f310655 1177{
5517260a 1178 Ftp::Client::handleControlReply();
434a79b0
DK
1179 if (ctrl.message == NULL)
1180 return; // didn't get complete reply yet
62e76326 1181
434a79b0
DK
1182 /* Copy the message except for the last line to cwd_message to be
1183 * printed in error messages.
858783c9 1184 */
434a79b0
DK
1185 for (wordlist *w = ctrl.message; w && w->next; w = w->next) {
1186 cwd_message.append('\n');
1187 cwd_message.append(w->key);
0477a072 1188 }
62e76326 1189
6f0aab86 1190 FTP_SM_FUNCS[state] (this);
234967c9 1191}
1192
3fdadc70 1193/* ====================================================================== */
1194
1195static void
5517260a 1196ftpReadWelcome(Ftp::Gateway * ftpState)
3fdadc70 1197{
1198 int code = ftpState->ctrl.replycode;
a689bd4e 1199 debugs(9, 3, HERE);
62e76326 1200
e55f0142 1201 if (ftpState->flags.pasv_only)
95dc7ff4 1202 ++ ftpState->login_att;
62e76326 1203
3fdadc70 1204 if (code == 220) {
62e76326 1205 if (ftpState->ctrl.message) {
1206 if (strstr(ftpState->ctrl.message->key, "NetWare"))
1207 ftpState->flags.skip_whitespace = 1;
1208 }
1209
1210 ftpSendUser(ftpState);
cdc33f35 1211 } else if (code == 120) {
62e76326 1212 if (NULL != ftpState->ctrl.message)
a689bd4e 1213 debugs(9, DBG_IMPORTANT, "FTP server is busy: " << ftpState->ctrl.message->key);
62e76326 1214
1215 return;
3fdadc70 1216 } else {
62e76326 1217 ftpFail(ftpState);
3fdadc70 1218 }
1219}
1220
f381d699
AJ
1221/**
1222 * Translate FTP login failure into HTTP error
1223 * this is an attmpt to get the 407 message to show up outside Squid.
1224 * its NOT a general failure. But a correct FTP response type.
1225 */
1226void
5517260a 1227Ftp::Gateway::loginFailed()
f381d699
AJ
1228{
1229 ErrorState *err = NULL;
f381d699 1230
493e0ad0
AJ
1231 if ((state == SENT_USER || state == SENT_PASS) && ctrl.replycode >= 400) {
1232 if (ctrl.replycode == 421 || ctrl.replycode == 426) {
1233 // 421/426 - Service Overload - retry permitted.
955394ce 1234 err = new ErrorState(ERR_FTP_UNAVAILABLE, Http::scServiceUnavailable, fwd->request);
493e0ad0
AJ
1235 } else if (ctrl.replycode >= 430 && ctrl.replycode <= 439) {
1236 // 43x - Invalid or Credential Error - retry challenge required.
955394ce 1237 err = new ErrorState(ERR_FTP_FORBIDDEN, Http::scUnauthorized, fwd->request);
493e0ad0
AJ
1238 } else if (ctrl.replycode >= 530 && ctrl.replycode <= 539) {
1239 // 53x - Credentials Missing - retry challenge required
1240 if (password_url) // but they were in the URI! major fail.
955394ce 1241 err = new ErrorState(ERR_FTP_FORBIDDEN, Http::scForbidden, fwd->request);
f381d699 1242 else
955394ce 1243 err = new ErrorState(ERR_FTP_FORBIDDEN, Http::scUnauthorized, fwd->request);
f381d699 1244 }
b115733c
AJ
1245 }
1246
493e0ad0 1247 if (!err) {
f381d699
AJ
1248 ftpFail(this);
1249 return;
1250 }
1251
cd4d5d45
VL
1252 failed(ERR_NONE, ctrl.replycode, err);
1253 // any other problems are general falures.
f381d699 1254
f381d699 1255 HttpReply *newrep = err->BuildHttpReply();
913524f0 1256 delete err;
9da6b594
AJ
1257
1258#if HAVE_AUTH_MODULE_BASIC
f381d699
AJ
1259 /* add Authenticate header */
1260 newrep->header.putAuth("Basic", ftpRealm());
9da6b594 1261#endif
f381d699
AJ
1262
1263 // add it to the store entry for response....
1264 entry->replaceHttpReply(newrep);
1265 serverComplete();
1266}
1267
1268const char *
5517260a 1269Ftp::Gateway::ftpRealm()
f381d699
AJ
1270{
1271 static char realm[8192];
1272
1273 /* This request is not fully authenticated */
1274 if (!request) {
1275 snprintf(realm, 8192, "FTP %s unknown", user);
5c51bffb
AJ
1276 } else if (request->url.port() == 21) {
1277 snprintf(realm, 8192, "FTP %s %s", user, request->url.host());
f381d699 1278 } else {
5c51bffb 1279 snprintf(realm, 8192, "FTP %s %s port %d", user, request->url.host(), request->url.port());
f381d699
AJ
1280 }
1281 return realm;
1282}
1283
969c39b9 1284static void
5517260a 1285ftpSendUser(Ftp::Gateway * ftpState)
969c39b9 1286{
a11382aa 1287 /* check the server control channel is still available */
9e008dda 1288 if (!ftpState || !ftpState->haveControlChannel("ftpSendUser"))
a11382aa 1289 return;
1290
969c39b9 1291 if (ftpState->proxy_host != NULL)
5c51bffb 1292 snprintf(cbuf, CTRL_BUFLEN, "USER %s@%s\r\n", ftpState->user, ftpState->request->url.host());
969c39b9 1293 else
903198a7 1294 snprintf(cbuf, CTRL_BUFLEN, "USER %s\r\n", ftpState->user);
62e76326 1295
2f47fadf 1296 ftpState->writeCommand(cbuf);
62e76326 1297
5517260a 1298 ftpState->state = Ftp::Client::SENT_USER;
969c39b9 1299}
1300
3fdadc70 1301static void
5517260a 1302ftpReadUser(Ftp::Gateway * ftpState)
234967c9 1303{
3fdadc70 1304 int code = ftpState->ctrl.replycode;
a689bd4e 1305 debugs(9, 3, HERE);
62e76326 1306
3fdadc70 1307 if (code == 230) {
62e76326 1308 ftpReadPass(ftpState);
3fdadc70 1309 } else if (code == 331) {
62e76326 1310 ftpSendPass(ftpState);
3fdadc70 1311 } else {
f381d699 1312 ftpState->loginFailed();
3fdadc70 1313 }
1314}
1315
969c39b9 1316static void
5517260a 1317ftpSendPass(Ftp::Gateway * ftpState)
969c39b9 1318{
a11382aa 1319 /* check the server control channel is still available */
9e008dda 1320 if (!ftpState || !ftpState->haveControlChannel("ftpSendPass"))
a11382aa 1321 return;
1322
903198a7 1323 snprintf(cbuf, CTRL_BUFLEN, "PASS %s\r\n", ftpState->password);
2f47fadf 1324 ftpState->writeCommand(cbuf);
5517260a 1325 ftpState->state = Ftp::Client::SENT_PASS;
969c39b9 1326}
1327
3fdadc70 1328static void
5517260a 1329ftpReadPass(Ftp::Gateway * ftpState)
3fdadc70 1330{
1331 int code = ftpState->ctrl.replycode;
4d6c56a6 1332 debugs(9, 3, HERE << "code=" << code);
62e76326 1333
3fdadc70 1334 if (code == 230) {
62e76326 1335 ftpSendType(ftpState);
3fdadc70 1336 } else {
f381d699 1337 ftpState->loginFailed();
3fdadc70 1338 }
1339}
1340
969c39b9 1341static void
5517260a 1342ftpSendType(Ftp::Gateway * ftpState)
969c39b9 1343{
a11382aa 1344 /* check the server control channel is still available */
9e008dda 1345 if (!ftpState || !ftpState->haveControlChannel("ftpSendType"))
a11382aa 1346 return;
1347
9e242e02 1348 /*
1349 * Ref section 3.2.2 of RFC 1738
1350 */
51b5dcf5 1351 char mode = ftpState->typecode;
62e76326 1352
b02bfc2d 1353 switch (mode) {
62e76326 1354
9e242e02 1355 case 'D':
62e76326 1356 mode = 'A';
1357 break;
1358
9e242e02 1359 case 'A':
62e76326 1360
9e242e02 1361 case 'I':
62e76326 1362 break;
1363
9e242e02 1364 default:
62e76326 1365
1366 if (ftpState->flags.isdir) {
1367 mode = 'A';
1368 } else {
51b5dcf5
AJ
1369 auto t = ftpState->request->url.path().rfind('/');
1370 // XXX: performance regression, c_str() may reallocate
1371 SBuf filename = ftpState->request->url.path().substr(t != SBuf::npos ? t + 1 : 0);
1372 mode = mimeGetTransferMode(filename.c_str());
62e76326 1373 }
1374
1375 break;
9e242e02 1376 }
62e76326 1377
969c39b9 1378 if (mode == 'I')
62e76326 1379 ftpState->flags.binary = 1;
cfbf5373 1380 else
62e76326 1381 ftpState->flags.binary = 0;
1382
903198a7 1383 snprintf(cbuf, CTRL_BUFLEN, "TYPE %c\r\n", mode);
62e76326 1384
2f47fadf 1385 ftpState->writeCommand(cbuf);
62e76326 1386
5517260a 1387 ftpState->state = Ftp::Client::SENT_TYPE;
969c39b9 1388}
1389
3fdadc70 1390static void
5517260a 1391ftpReadType(Ftp::Gateway * ftpState)
3fdadc70 1392{
1393 int code = ftpState->ctrl.replycode;
3fdadc70 1394 char *path;
6e5ae4a4 1395 char *d, *p;
6200d3d6 1396 debugs(9, 3, HERE << "code=" << code);
62e76326 1397
3fdadc70 1398 if (code == 200) {
3f0e38d6 1399 p = path = SBufToCstring(ftpState->request->url.path());
62e76326 1400
1401 if (*p == '/')
95dc7ff4 1402 ++p;
62e76326 1403
1404 while (*p) {
1405 d = p;
1406 p += strcspn(p, "/");
1407
a38ec4b1
FC
1408 if (*p) {
1409 *p = '\0';
1410 ++p;
1411 }
62e76326 1412
1413 rfc1738_unescape(d);
1414
80dc929d 1415 if (*d)
1416 wordlistAdd(&ftpState->pathcomps, d);
62e76326 1417 }
1418
1419 xfree(path);
1420
1421 if (ftpState->pathcomps)
1422 ftpTraverseDirectory(ftpState);
1423 else
1424 ftpListDir(ftpState);
3fdadc70 1425 } else {
62e76326 1426 ftpFail(ftpState);
3fdadc70 1427 }
1428}
1429
1430static void
5517260a 1431ftpTraverseDirectory(Ftp::Gateway * ftpState)
3fdadc70 1432{
a689bd4e 1433 debugs(9, 4, HERE << (ftpState->filepath ? ftpState->filepath : "<NULL>"));
969c39b9 1434
5627a633 1435 safe_free(ftpState->dirpath);
1436 ftpState->dirpath = ftpState->filepath;
1437 ftpState->filepath = NULL;
1438
969c39b9 1439 /* Done? */
62e76326 1440
969c39b9 1441 if (ftpState->pathcomps == NULL) {
a689bd4e 1442 debugs(9, 3, HERE << "the final component was a directory");
62e76326 1443 ftpListDir(ftpState);
1444 return;
3fdadc70 1445 }
62e76326 1446
969c39b9 1447 /* Go to next path component */
0d225c05 1448 ftpState->filepath = wordlistChopHead(& ftpState->pathcomps);
62e76326 1449
969c39b9 1450 /* Check if we are to CWD or RETR */
e55f0142 1451 if (ftpState->pathcomps != NULL || ftpState->flags.isdir) {
62e76326 1452 ftpSendCwd(ftpState);
969c39b9 1453 } else {
a689bd4e 1454 debugs(9, 3, HERE << "final component is probably a file");
62e76326 1455 ftpGetFile(ftpState);
1456 return;
969c39b9 1457 }
1458}
1459
1460static void
5517260a 1461ftpSendCwd(Ftp::Gateway * ftpState)
969c39b9 1462{
9ededfe2 1463 char *path = NULL;
a11382aa 1464
1465 /* check the server control channel is still available */
9e008dda 1466 if (!ftpState || !ftpState->haveControlChannel("ftpSendCwd"))
a11382aa 1467 return;
1468
a689bd4e 1469 debugs(9, 3, HERE);
62e76326 1470
9ededfe2 1471 path = ftpState->filepath;
1472
969c39b9 1473 if (!strcmp(path, "..") || !strcmp(path, "/")) {
62e76326 1474 ftpState->flags.no_dotdot = 1;
13e80e5b 1475 } else {
62e76326 1476 ftpState->flags.no_dotdot = 0;
13e80e5b 1477 }
62e76326 1478
903198a7 1479 snprintf(cbuf, CTRL_BUFLEN, "CWD %s\r\n", path);
62e76326 1480
2f47fadf 1481 ftpState->writeCommand(cbuf);
62e76326 1482
5517260a 1483 ftpState->state = Ftp::Client::SENT_CWD;
3fdadc70 1484}
77a30ebb 1485
3fdadc70 1486static void
5517260a 1487ftpReadCwd(Ftp::Gateway * ftpState)
3fdadc70 1488{
1489 int code = ftpState->ctrl.replycode;
a689bd4e 1490 debugs(9, 3, HERE);
62e76326 1491
3fdadc70 1492 if (code >= 200 && code < 300) {
62e76326 1493 /* CWD OK */
6f0aab86 1494 ftpState->unhack();
62e76326 1495
0477a072
AJ
1496 /* Reset cwd_message to only include the last message */
1497 ftpState->cwd_message.reset("");
1498 for (wordlist *w = ftpState->ctrl.message; w; w = w->next) {
1499 ftpState->cwd_message.append(' ');
1500 ftpState->cwd_message.append(w->key);
1501 }
62e76326 1502 ftpState->ctrl.message = NULL;
1503
1504 /* Continue to traverse the path */
1505 ftpTraverseDirectory(ftpState);
3fdadc70 1506 } else {
62e76326 1507 /* CWD FAILED */
1508
1509 if (!ftpState->flags.put)
1510 ftpFail(ftpState);
1511 else
1512 ftpSendMkdir(ftpState);
c021888f 1513 }
3fdadc70 1514}
1515
54220df8 1516static void
5517260a 1517ftpSendMkdir(Ftp::Gateway * ftpState)
54220df8 1518{
9ededfe2 1519 char *path = NULL;
a11382aa 1520
1521 /* check the server control channel is still available */
9e008dda 1522 if (!ftpState || !ftpState->haveControlChannel("ftpSendMkdir"))
a11382aa 1523 return;
1524
9ededfe2 1525 path = ftpState->filepath;
a689bd4e 1526 debugs(9, 3, HERE << "with path=" << path);
903198a7 1527 snprintf(cbuf, CTRL_BUFLEN, "MKD %s\r\n", path);
2f47fadf 1528 ftpState->writeCommand(cbuf);
5517260a 1529 ftpState->state = Ftp::Client::SENT_MKDIR;
54220df8 1530}
1531
1532static void
5517260a 1533ftpReadMkdir(Ftp::Gateway * ftpState)
4162ee3b 1534{
1535 char *path = ftpState->filepath;
1536 int code = ftpState->ctrl.replycode;
1537
a689bd4e 1538 debugs(9, 3, HERE << "path " << path << ", code " << code);
62e76326 1539
f53969cc 1540 if (code == 257) { /* success */
62e76326 1541 ftpSendCwd(ftpState);
f53969cc 1542 } else if (code == 550) { /* dir exists */
62e76326 1543
1544 if (ftpState->flags.put_mkdir) {
1545 ftpState->flags.put_mkdir = 1;
1546 ftpSendCwd(ftpState);
1547 } else
1548 ftpSendReply(ftpState);
4162ee3b 1549 } else
62e76326 1550 ftpSendReply(ftpState);
54220df8 1551}
1552
dbfed404 1553static void
5517260a 1554ftpGetFile(Ftp::Gateway * ftpState)
dbfed404 1555{
1556 assert(*ftpState->filepath != '\0');
e55f0142 1557 ftpState->flags.isdir = 0;
dbfed404 1558 ftpSendMdtm(ftpState);
1559}
1560
1561static void
5517260a 1562ftpListDir(Ftp::Gateway * ftpState)
dbfed404 1563{
d5f80edc 1564 if (ftpState->flags.dir_slash) {
a689bd4e 1565 debugs(9, 3, HERE << "Directory path did not end in /");
62e76326 1566 ftpState->title_url.append("/");
1567 ftpState->flags.isdir = 1;
dbfed404 1568 }
62e76326 1569
a689bd4e 1570 ftpSendPassive(ftpState);
dbfed404 1571}
1572
969c39b9 1573static void
5517260a 1574ftpSendMdtm(Ftp::Gateway * ftpState)
969c39b9 1575{
a11382aa 1576 /* check the server control channel is still available */
9e008dda 1577 if (!ftpState || !ftpState->haveControlChannel("ftpSendMdtm"))
a11382aa 1578 return;
1579
969c39b9 1580 assert(*ftpState->filepath != '\0');
903198a7 1581 snprintf(cbuf, CTRL_BUFLEN, "MDTM %s\r\n", ftpState->filepath);
2f47fadf 1582 ftpState->writeCommand(cbuf);
5517260a 1583 ftpState->state = Ftp::Client::SENT_MDTM;
969c39b9 1584}
1585
3fdadc70 1586static void
5517260a 1587ftpReadMdtm(Ftp::Gateway * ftpState)
3fdadc70 1588{
1589 int code = ftpState->ctrl.replycode;
a689bd4e 1590 debugs(9, 3, HERE);
62e76326 1591
3fdadc70 1592 if (code == 213) {
62e76326 1593 ftpState->mdtm = parse_iso3307_time(ftpState->ctrl.last_reply);
6f0aab86 1594 ftpState->unhack();
3fdadc70 1595 } else if (code < 0) {
62e76326 1596 ftpFail(ftpState);
9e008dda 1597 return;
77a30ebb 1598 }
62e76326 1599
969c39b9 1600 ftpSendSize(ftpState);
1601}
1602
1603static void
5517260a 1604ftpSendSize(Ftp::Gateway * ftpState)
969c39b9 1605{
a11382aa 1606 /* check the server control channel is still available */
9e008dda 1607 if (!ftpState || !ftpState->haveControlChannel("ftpSendSize"))
a11382aa 1608 return;
1609
969c39b9 1610 /* Only send SIZE for binary transfers. The returned size
1611 * is useless on ASCII transfers */
62e76326 1612
e55f0142 1613 if (ftpState->flags.binary) {
62e76326 1614 assert(ftpState->filepath != NULL);
1615 assert(*ftpState->filepath != '\0');
903198a7 1616 snprintf(cbuf, CTRL_BUFLEN, "SIZE %s\r\n", ftpState->filepath);
2f47fadf 1617 ftpState->writeCommand(cbuf);
5517260a 1618 ftpState->state = Ftp::Client::SENT_SIZE;
969c39b9 1619 } else
62e76326 1620 /* Skip to next state no non-binary transfers */
a689bd4e 1621 ftpSendPassive(ftpState);
3fdadc70 1622}
1623
1624static void
5517260a 1625ftpReadSize(Ftp::Gateway * ftpState)
3fdadc70 1626{
1627 int code = ftpState->ctrl.replycode;
a689bd4e 1628 debugs(9, 3, HERE);
62e76326 1629
3fdadc70 1630 if (code == 213) {
6f0aab86 1631 ftpState->unhack();
47f6e231 1632 ftpState->theSize = strtoll(ftpState->ctrl.last_reply, NULL, 10);
62e76326 1633
47f6e231 1634 if (ftpState->theSize == 0) {
a689bd4e 1635 debugs(9, 2, "SIZE reported " <<
9e008dda 1636 ftpState->ctrl.last_reply << " on " <<
d53b3f6d 1637 ftpState->title_url);
47f6e231 1638 ftpState->theSize = -1;
62e76326 1639 }
3fdadc70 1640 } else if (code < 0) {
62e76326 1641 ftpFail(ftpState);
9e008dda 1642 return;
3fdadc70 1643 }
62e76326 1644
a689bd4e 1645 ftpSendPassive(ftpState);
3fdadc70 1646}
1647
cc192b50 1648static void
5517260a 1649ftpReadEPSV(Ftp::Gateway* ftpState)
cc192b50 1650{
000e664b
AR
1651 Ip::Address srvAddr; // unused
1652 if (ftpState->handleEpsvReply(srvAddr)) {
1653 if (ftpState->ctrl.message == NULL)
1654 return; // didn't get complete reply yet
a689bd4e 1655
000e664b 1656 ftpState->connectDataChannel();
a689bd4e 1657 }
a689bd4e 1658}
cc192b50 1659
3cc0f4e7 1660/** Send Passive connection request.
a689bd4e 1661 * Default method is to use modern EPSV request.
1662 * The failover mechanism should check for previous state and re-call with alternates on failure.
1663 */
3fdadc70 1664static void
5517260a 1665ftpSendPassive(Ftp::Gateway * ftpState)
3fdadc70 1666{
a689bd4e 1667 /** Checks the server control channel is still available before running. */
9e008dda 1668 if (!ftpState || !ftpState->haveControlChannel("ftpSendPassive"))
a11382aa 1669 return;
1670
a689bd4e 1671 debugs(9, 3, HERE);
1672
a689bd4e 1673 /** \par
5517260a 1674 * Checks for 'HEAD' method request and passes off for special handling by Ftp::Gateway::processHeadResponse(). */
c2a7cefd 1675 if (ftpState->request->method == Http::METHOD_HEAD && (ftpState->flags.isdir || ftpState->theSize != -1)) {
063a47b5 1676 ftpState->processHeadResponse(); // may call serverComplete
62e76326 1677 return;
7497ceef 1678 }
62e76326 1679
f8e4867b
AR
1680 if (ftpState->sendPassive()) {
1681 // SENT_EPSV_ALL blocks other non-EPSV connections being attempted
5517260a 1682 if (ftpState->state == Ftp::Client::SENT_EPSV_ALL)
a689bd4e 1683 ftpState->flags.epsv_all_sent = true;
a689bd4e 1684 }
3fdadc70 1685}
1686
063a47b5 1687void
5517260a 1688Ftp::Gateway::processHeadResponse()
063a47b5 1689{
1690 debugs(9, 5, HERE << "handling HEAD response");
1691 ftpSendQuit(this);
1692 appendSuccessHeader();
1693
1694 /*
1695 * On rare occasions I'm seeing the entry get aborted after
434a79b0 1696 * readControlReply() and before here, probably when
063a47b5 1697 * trying to write to the client.
1698 */
1699 if (EBIT_TEST(entry->flags, ENTRY_ABORTED)) {
1700 abortTransaction("entry aborted while processing HEAD");
1701 return;
1702 }
1703
a83c6ed6
AR
1704#if USE_ADAPTATION
1705 if (adaptationAccessCheckPending) {
1706 debugs(9,3, HERE << "returning due to adaptationAccessCheckPending");
063a47b5 1707 return;
1708 }
1709#endif
1710
1711 // processReplyBody calls serverComplete() since there is no body
9e008dda 1712 processReplyBody();
063a47b5 1713}
1714
3fdadc70 1715static void
5517260a 1716ftpReadPasv(Ftp::Gateway * ftpState)
3fdadc70 1717{
73950ceb
AR
1718 Ip::Address srvAddr; // unused
1719 if (ftpState->handlePasvReply(srvAddr))
434a79b0
DK
1720 ftpState->connectDataChannel();
1721 else {
cc192b50 1722 ftpSendEPRT(ftpState);
62e76326 1723 return;
7f74df3a 1724 }
3fdadc70 1725}
1726
6f0aab86 1727void
43446566 1728Ftp::Gateway::dataChannelConnected(const CommConnectCbParams &io)
3fdadc70 1729{
a689bd4e 1730 debugs(9, 3, HERE);
434a79b0 1731 data.opener = NULL;
62e76326 1732
43446566 1733 if (io.flag != Comm::OK) {
6200d3d6 1734 debugs(9, 2, HERE << "Failed to connect. Retrying via another method.");
6200d3d6
AJ
1735
1736 // ABORT on timeouts. server may be waiting on a broken TCP link.
43446566 1737 if (io.xerrno == Comm::TIMEOUT)
434a79b0 1738 writeCommand("ABOR");
6200d3d6
AJ
1739
1740 // try another connection attempt with some other method
434a79b0 1741 ftpSendPassive(this);
62e76326 1742 return;
3fdadc70 1743 }
62e76326 1744
43446566 1745 data.opened(io.conn, dataCloser());
434a79b0 1746 ftpRestOrList(this);
3fdadc70 1747}
1748
903198a7 1749static void
5517260a 1750ftpOpenListenSocket(Ftp::Gateway * ftpState, int fallback)
cdc33f35 1751{
04f55905 1752 /// Close old data channels, if any. We may open a new one below.
b2e3fa62
AJ
1753 if (ftpState->data.conn != NULL) {
1754 if ((ftpState->data.conn->flags & COMM_REUSEADDR))
1755 // NP: in fact it points to the control channel. just clear it.
1756 ftpState->data.clear();
1757 else
1758 ftpState->data.close();
1759 }
a9de4717 1760 safe_free(ftpState->data.host);
62e76326 1761
4f310655 1762 /*
1763 * Set up a listen socket on the same local address as the
1764 * control connection.
1765 */
8bbb16e3
AJ
1766 Comm::ConnectionPointer temp = new Comm::Connection;
1767 temp->local = ftpState->ctrl.conn->local;
62e76326 1768
4f310655 1769 /*
1770 * REUSEADDR is needed in fallback mode, since the same port is
1771 * used for both control and data.
cdc33f35 1772 */
1773 if (fallback) {
6200d3d6 1774 int on = 1;
6cf3e435
FC
1775 errno = 0;
1776 if (setsockopt(ftpState->ctrl.conn->fd, SOL_SOCKET, SO_REUSEADDR,
203fdebd 1777 (char *) &on, sizeof(on)) == -1) {
6cf3e435
FC
1778 // SO_REUSEADDR is only an optimization, no need to be verbose about error
1779 debugs(9, 4, "setsockopt failed: " << xstrerror());
1780 }
6b679a01 1781 ftpState->ctrl.conn->flags |= COMM_REUSEADDR;
8bbb16e3 1782 temp->flags |= COMM_REUSEADDR;
9e008dda 1783 } else {
a689bd4e 1784 /* if not running in fallback mode a new port needs to be retrieved */
4dd643d5 1785 temp->local.port(0);
cdc33f35 1786 }
62e76326 1787
434a79b0 1788 ftpState->listenForDataChannel(temp);
cdc33f35 1789}
1790
3fdadc70 1791static void
5517260a 1792ftpSendPORT(Ftp::Gateway * ftpState)
3fdadc70 1793{
a11382aa 1794 /* check the server control channel is still available */
9e008dda 1795 if (!ftpState || !ftpState->haveControlChannel("ftpSendPort"))
a11382aa 1796 return;
1797
9e008dda 1798 if (Config.Ftp.epsv_all && ftpState->flags.epsv_all_sent) {
a689bd4e 1799 debugs(9, DBG_IMPORTANT, "FTP does not allow PORT method after 'EPSV ALL' has been sent.");
1800 return;
1801 }
1802
1803 debugs(9, 3, HERE);
e55f0142 1804 ftpState->flags.pasv_supported = 0;
903198a7 1805 ftpOpenListenSocket(ftpState, 0);
6b679a01 1806
00406b24 1807 if (!Comm::IsConnOpen(ftpState->data.listenConn)) {
4dd643d5 1808 if ( ftpState->data.listenConn != NULL && !ftpState->data.listenConn->local.isIPv4() ) {
6b679a01
AJ
1809 /* non-IPv4 CANNOT send PORT command. */
1810 /* we got here by attempting and failing an EPRT */
1811 /* using the same reply code should simulate a PORT failure */
1812 ftpReadPORT(ftpState);
1813 return;
1814 }
bf8fe701 1815
62e76326 1816 /* XXX Need to set error message */
1817 ftpFail(ftpState);
1818 return;
cdc33f35 1819 }
62e76326 1820
6200d3d6
AJ
1821 // pull out the internal IP address bytes to send in PORT command...
1822 // source them from the listen_conn->local
cc192b50 1823
6200d3d6 1824 struct addrinfo *AI = NULL;
4dd643d5 1825 ftpState->data.listenConn->local.getAddrInfo(AI, AF_INET);
6200d3d6
AJ
1826 unsigned char *addrptr = (unsigned char *) &((struct sockaddr_in*)AI->ai_addr)->sin_addr;
1827 unsigned char *portptr = (unsigned char *) &((struct sockaddr_in*)AI->ai_addr)->sin_port;
903198a7 1828 snprintf(cbuf, CTRL_BUFLEN, "PORT %d,%d,%d,%d,%d,%d\r\n",
62e76326 1829 addrptr[0], addrptr[1], addrptr[2], addrptr[3],
1830 portptr[0], portptr[1]);
2f47fadf 1831 ftpState->writeCommand(cbuf);
5517260a 1832 ftpState->state = Ftp::Client::SENT_PORT;
cc192b50 1833
851614a8 1834 Ip::Address::FreeAddr(AI);
3fdadc70 1835}
1836
1837static void
5517260a 1838ftpReadPORT(Ftp::Gateway * ftpState)
3fdadc70 1839{
cdc33f35 1840 int code = ftpState->ctrl.replycode;
a689bd4e 1841 debugs(9, 3, HERE);
62e76326 1842
cdc33f35 1843 if (code != 200) {
62e76326 1844 /* Fall back on using the same port as the control connection */
bf8fe701 1845 debugs(9, 3, "PORT not supported by remote end");
62e76326 1846 ftpOpenListenSocket(ftpState, 1);
cdc33f35 1847 }
62e76326 1848
cdc33f35 1849 ftpRestOrList(ftpState);
1850}
1851
cc192b50 1852static void
5517260a 1853ftpSendEPRT(Ftp::Gateway * ftpState)
cc192b50 1854{
9e008dda 1855 if (Config.Ftp.epsv_all && ftpState->flags.epsv_all_sent) {
a689bd4e 1856 debugs(9, DBG_IMPORTANT, "FTP does not allow EPRT method after 'EPSV ALL' has been sent.");
1857 return;
1858 }
1859
63ee5443
AJ
1860 if (!Config.Ftp.eprt) {
1861 /* Disabled. Switch immediately to attempting old PORT command. */
1862 debugs(9, 3, "EPRT disabled by local administrator");
1863 ftpSendPORT(ftpState);
1864 return;
1865 }
1866
a689bd4e 1867 debugs(9, 3, HERE);
cc192b50 1868 ftpState->flags.pasv_supported = 0;
903198a7
AJ
1869
1870 ftpOpenListenSocket(ftpState, 0);
8bbb16e3
AJ
1871 debugs(9, 3, "Listening for FTP data connection with FD " << ftpState->data.conn);
1872 if (!Comm::IsConnOpen(ftpState->data.conn)) {
903198a7
AJ
1873 /* XXX Need to set error message */
1874 ftpFail(ftpState);
1875 return;
1876 }
cc192b50 1877
8bbb16e3 1878 char buf[MAX_IPSTRLEN];
cc192b50 1879
1880 /* RFC 2428 defines EPRT as IPv6 equivalent to IPv4 PORT command. */
1881 /* Which can be used by EITHER protocol. */
903198a7 1882 snprintf(cbuf, CTRL_BUFLEN, "EPRT |%d|%s|%d|\r\n",
4dd643d5
AJ
1883 ( ftpState->data.listenConn->local.isIPv6() ? 2 : 1 ),
1884 ftpState->data.listenConn->local.toStr(buf,MAX_IPSTRLEN),
1885 ftpState->data.listenConn->local.port() );
cc192b50 1886
1887 ftpState->writeCommand(cbuf);
5517260a 1888 ftpState->state = Ftp::Client::SENT_EPRT;
cc192b50 1889}
1890
1891static void
5517260a 1892ftpReadEPRT(Ftp::Gateway * ftpState)
cc192b50 1893{
1894 int code = ftpState->ctrl.replycode;
a689bd4e 1895 debugs(9, 3, HERE);
cc192b50 1896
1897 if (code != 200) {
1898 /* Failover to attempting old PORT command. */
1899 debugs(9, 3, "EPRT not supported by remote end");
1900 ftpSendPORT(ftpState);
1901 return;
1902 }
1903
1904 ftpRestOrList(ftpState);
1905}
1906
3cc0f4e7 1907/** "read" handler to accept FTP data connections.
a689bd4e 1908 *
63be0a78 1909 \param io comm accept(2) callback parameters
a689bd4e 1910 */
6200d3d6 1911void
5517260a 1912Ftp::Gateway::ftpAcceptDataConnection(const CommAcceptCbParams &io)
cdc33f35 1913{
cbff89ba 1914 debugs(9, 3, HERE);
cdc33f35 1915
dc56a9b1 1916 if (EBIT_TEST(entry->flags, ENTRY_ABORTED)) {
1917 abortTransaction("entry aborted when accepting data conn");
00406b24
AJ
1918 data.listenConn->close();
1919 data.listenConn = NULL;
62e76326 1920 return;
b044a7eb 1921 }
c4b7a5a9 1922
c8407295 1923 if (io.flag != Comm::OK) {
00406b24
AJ
1924 data.listenConn->close();
1925 data.listenConn = NULL;
1926 debugs(9, DBG_IMPORTANT, "FTP AcceptDataConnection: " << io.conn << ": " << xstrerr(io.xerrno));
1927 /** \todo Need to send error message on control channel*/
903198a7
AJ
1928 ftpFail(this);
1929 return;
1930 }
1931
00406b24
AJ
1932 /* data listening conn is no longer even open. abort. */
1933 if (!Comm::IsConnOpen(data.listenConn)) {
1934 data.listenConn = NULL; // ensure that it's cleared and not just closed.
1935 return;
1936 }
1937
cbff89ba 1938 /* data listening conn is no longer even open. abort. */
8bbb16e3 1939 if (!Comm::IsConnOpen(data.conn)) {
cbff89ba
AJ
1940 data.clear(); // ensure that it's cleared and not just closed.
1941 return;
1942 }
1943
a689bd4e 1944 /** \par
1945 * When squid.conf ftp_sanitycheck is enabled, check the new connection is actually being
1946 * made by the remote client which is connected to the FTP control socket.
00406b24 1947 * Or the one which we were told to listen for by control channel messages (may differ under NAT).
a689bd4e 1948 * This prevents third-party hacks, but also third-party load balancing handshakes.
1949 */
00c5afca 1950 if (Config.Ftp.sanitycheck) {
6200d3d6 1951 // accept if either our data or ctrl connection is talking to this remote peer.
8bbb16e3 1952 if (data.conn->remote != io.conn->remote && ctrl.conn->remote != io.conn->remote) {
9e008dda
AJ
1953 debugs(9, DBG_IMPORTANT,
1954 "FTP data connection from unexpected server (" <<
00406b24 1955 io.conn->remote << "), expecting " <<
8bbb16e3 1956 data.conn->remote << " or " << ctrl.conn->remote);
dc56a9b1 1957
cbff89ba 1958 /* close the bad sources connection down ASAP. */
8bbb16e3 1959 io.conn->close();
04f55905 1960
00406b24 1961 /* drop the bad connection (io) by ignoring the attempt. */
62e76326 1962 return;
1963 }
00c5afca 1964 }
62e76326 1965
c8407295 1966 /** On Comm::OK start using the accepted data socket and discard the temporary listen socket. */
94b88585 1967 data.close();
00406b24 1968 data.opened(io.conn, dataCloser());
73950ceb 1969 data.addr(io.conn->remote);
a689bd4e 1970
903198a7 1971 debugs(9, 3, HERE << "Connected data socket on " <<
00406b24 1972 io.conn << ". FD table says: " <<
6b679a01
AJ
1973 "ctrl-peer= " << fd_table[ctrl.conn->fd].ipaddr << ", " <<
1974 "data-peer= " << fd_table[data.conn->fd].ipaddr);
a689bd4e 1975
903198a7
AJ
1976 assert(haveControlChannel("ftpAcceptDataConnection"));
1977 assert(ctrl.message == NULL);
a689bd4e 1978
903198a7 1979 // Ctrl channel operations will determine what happens to this data connection
3fdadc70 1980}
1981
1982static void
5517260a 1983ftpRestOrList(Ftp::Gateway * ftpState)
3fdadc70 1984{
a689bd4e 1985 debugs(9, 3, HERE);
62e76326 1986
94439e4e 1987 if (ftpState->typecode == 'D') {
62e76326 1988 ftpState->flags.isdir = 1;
62e76326 1989
1990 if (ftpState->flags.put) {
f53969cc 1991 ftpSendMkdir(ftpState); /* PUT name;type=d */
62e76326 1992 } else {
f53969cc 1993 ftpSendNlst(ftpState); /* GET name;type=d sec 3.2.2 of RFC 1738 */
62e76326 1994 }
94439e4e 1995 } else if (ftpState->flags.put) {
62e76326 1996 ftpSendStor(ftpState);
e55f0142 1997 } else if (ftpState->flags.isdir)
62e76326 1998 ftpSendList(ftpState);
6f0aab86 1999 else if (ftpState->restartable())
62e76326 2000 ftpSendRest(ftpState);
969c39b9 2001 else
62e76326 2002 ftpSendRetr(ftpState);
969c39b9 2003}
2004
54220df8 2005static void
5517260a 2006ftpSendStor(Ftp::Gateway * ftpState)
54220df8 2007{
a11382aa 2008 /* check the server control channel is still available */
9e008dda 2009 if (!ftpState || !ftpState->haveControlChannel("ftpSendStor"))
a11382aa 2010 return;
2011
a689bd4e 2012 debugs(9, 3, HERE);
2013
9bc73deb 2014 if (ftpState->filepath != NULL) {
62e76326 2015 /* Plain file upload */
903198a7 2016 snprintf(cbuf, CTRL_BUFLEN, "STOR %s\r\n", ftpState->filepath);
2f47fadf 2017 ftpState->writeCommand(cbuf);
5517260a 2018 ftpState->state = Ftp::Client::SENT_STOR;
789217a2 2019 } else if (ftpState->request->header.getInt64(Http::HdrType::CONTENT_LENGTH) > 0) {
62e76326 2020 /* File upload without a filename. use STOU to generate one */
903198a7 2021 snprintf(cbuf, CTRL_BUFLEN, "STOU\r\n");
2f47fadf 2022 ftpState->writeCommand(cbuf);
5517260a 2023 ftpState->state = Ftp::Client::SENT_STOR;
9bc73deb 2024 } else {
62e76326 2025 /* No file to transfer. Only create directories if needed */
2026 ftpSendReply(ftpState);
9bc73deb 2027 }
54220df8 2028}
2029
63be0a78 2030/// \deprecated use ftpState->readStor() instead.
54220df8 2031static void
5517260a 2032ftpReadStor(Ftp::Gateway * ftpState)
54220df8 2033{
5f8252d2 2034 ftpState->readStor();
2035}
2036
5517260a 2037void Ftp::Gateway::readStor()
9e008dda 2038{
5f8252d2 2039 int code = ctrl.replycode;
a689bd4e 2040 debugs(9, 3, HERE);
62e76326 2041
d890b809 2042 if (code == 125 || (code == 150 && Comm::IsConnOpen(data.conn))) {
ccc927f9
AR
2043 if (!originalRequest()->body_pipe) {
2044 debugs(9, 3, "zero-size STOR?");
2045 state = WRITING_DATA; // make ftpWriteTransferDone() responsible
2046 dataComplete(); // XXX: keep in sync with doneSendingRequestBody()
2047 return;
2048 }
2049
123ec4de 2050 if (!startRequestBodyFlow()) { // register to receive body data
5f8252d2 2051 ftpFail(this);
2052 return;
2053 }
2054
d890b809 2055 /* When client status is 125, or 150 and the data connection is open, Begin data transfer. */
a689bd4e 2056 debugs(9, 3, HERE << "starting data transfer");
d890b809 2057 switchTimeoutToDataChannel();
5f8252d2 2058 sendMoreRequestBody();
d890b809 2059 fwd->dontRetry(true); // dont permit re-trying if the body was sent.
5f8252d2 2060 state = WRITING_DATA;
a689bd4e 2061 debugs(9, 3, HERE << "writing data channel");
9bc73deb 2062 } else if (code == 150) {
d890b809 2063 /* When client code is 150 with no data channel, Accept data channel. */
dc56a9b1 2064 debugs(9, 3, "ftpReadStor: accepting data channel");
434a79b0 2065 listenForDataChannel(data.conn);
54220df8 2066 } else {
a689bd4e 2067 debugs(9, DBG_IMPORTANT, HERE << "Unexpected reply code "<< std::setfill('0') << std::setw(3) << code);
5f8252d2 2068 ftpFail(this);
54220df8 2069 }
2070}
2071
969c39b9 2072static void
5517260a 2073ftpSendRest(Ftp::Gateway * ftpState)
969c39b9 2074{
a11382aa 2075 /* check the server control channel is still available */
9e008dda 2076 if (!ftpState || !ftpState->haveControlChannel("ftpSendRest"))
a11382aa 2077 return;
2078
a689bd4e 2079 debugs(9, 3, HERE);
2080
c91ca3ce 2081 snprintf(cbuf, CTRL_BUFLEN, "REST %" PRId64 "\r\n", ftpState->restart_offset);
2f47fadf 2082 ftpState->writeCommand(cbuf);
5517260a 2083 ftpState->state = Ftp::Client::SENT_REST;
3fdadc70 2084}
2085
6f0aab86 2086int
5517260a 2087Ftp::Gateway::restartable()
cfbf5373 2088{
6f0aab86 2089 if (restart_offset > 0)
62e76326 2090 return 1;
2091
6f0aab86 2092 if (!request->range)
62e76326 2093 return 0;
2094
6f0aab86 2095 if (!flags.binary)
62e76326 2096 return 0;
2097
47f6e231 2098 if (theSize <= 0)
62e76326 2099 return 0;
cfbf5373 2100
47f6e231 2101 int64_t desired_offset = request->range->lowestOffset(theSize);
62e76326 2102
b0e52cb9 2103 if (desired_offset <= 0)
62e76326 2104 return 0;
2105
47f6e231 2106 if (desired_offset >= theSize)
9e008dda 2107 return 0;
b0e52cb9 2108
2109 restart_offset = desired_offset;
cfbf5373 2110 return 1;
2111}
2112
3fdadc70 2113static void
5517260a 2114ftpReadRest(Ftp::Gateway * ftpState)
3fdadc70 2115{
2116 int code = ftpState->ctrl.replycode;
a689bd4e 2117 debugs(9, 3, HERE);
3fdadc70 2118 assert(ftpState->restart_offset > 0);
62e76326 2119
3fdadc70 2120 if (code == 350) {
04f7fd38 2121 ftpState->setCurrentOffset(ftpState->restart_offset);
62e76326 2122 ftpSendRetr(ftpState);
3fdadc70 2123 } else if (code > 0) {
a689bd4e 2124 debugs(9, 3, HERE << "REST not supported");
62e76326 2125 ftpState->flags.rest_supported = 0;
2126 ftpSendRetr(ftpState);
3fdadc70 2127 } else {
62e76326 2128 ftpFail(ftpState);
3fdadc70 2129 }
2130}
2131
969c39b9 2132static void
5517260a 2133ftpSendList(Ftp::Gateway * ftpState)
969c39b9 2134{
a11382aa 2135 /* check the server control channel is still available */
9e008dda 2136 if (!ftpState || !ftpState->haveControlChannel("ftpSendList"))
a11382aa 2137 return;
2138
a689bd4e 2139 debugs(9, 3, HERE);
2140
dbfed404 2141 if (ftpState->filepath) {
903198a7 2142 snprintf(cbuf, CTRL_BUFLEN, "LIST %s\r\n", ftpState->filepath);
dbfed404 2143 } else {
903198a7 2144 snprintf(cbuf, CTRL_BUFLEN, "LIST\r\n");
dbfed404 2145 }
62e76326 2146
2f47fadf 2147 ftpState->writeCommand(cbuf);
5517260a 2148 ftpState->state = Ftp::Client::SENT_LIST;
969c39b9 2149}
2150
dbfed404 2151static void
5517260a 2152ftpSendNlst(Ftp::Gateway * ftpState)
dbfed404 2153{
a11382aa 2154 /* check the server control channel is still available */
9e008dda 2155 if (!ftpState || !ftpState->haveControlChannel("ftpSendNlst"))
a11382aa 2156 return;
2157
a689bd4e 2158 debugs(9, 3, HERE);
2159
e55f0142 2160 ftpState->flags.tried_nlst = 1;
62e76326 2161
dbfed404 2162 if (ftpState->filepath) {
903198a7 2163 snprintf(cbuf, CTRL_BUFLEN, "NLST %s\r\n", ftpState->filepath);
dbfed404 2164 } else {
903198a7 2165 snprintf(cbuf, CTRL_BUFLEN, "NLST\r\n");
dbfed404 2166 }
62e76326 2167
2f47fadf 2168 ftpState->writeCommand(cbuf);
5517260a 2169 ftpState->state = Ftp::Client::SENT_NLST;
dbfed404 2170}
2171
3fdadc70 2172static void
5517260a 2173ftpReadList(Ftp::Gateway * ftpState)
3fdadc70 2174{
2175 int code = ftpState->ctrl.replycode;
a689bd4e 2176 debugs(9, 3, HERE);
62e76326 2177
6200d3d6 2178 if (code == 125 || (code == 150 && Comm::IsConnOpen(ftpState->data.conn))) {
62e76326 2179 /* Begin data transfer */
8bbb16e3 2180 debugs(9, 3, HERE << "begin data transfer from " << ftpState->data.conn->remote << " (" << ftpState->data.conn->local << ")");
6200d3d6 2181 ftpState->switchTimeoutToDataChannel();
5f8252d2 2182 ftpState->maybeReadVirginBody();
5517260a 2183 ftpState->state = Ftp::Client::READING_DATA;
62e76326 2184 return;
cdc33f35 2185 } else if (code == 150) {
62e76326 2186 /* Accept data channel */
8bbb16e3 2187 debugs(9, 3, HERE << "accept data channel from " << ftpState->data.conn->remote << " (" << ftpState->data.conn->local << ")");
434a79b0 2188 ftpState->listenForDataChannel(ftpState->data.conn);
62e76326 2189 return;
e55f0142 2190 } else if (!ftpState->flags.tried_nlst && code > 300) {
62e76326 2191 ftpSendNlst(ftpState);
3fdadc70 2192 } else {
62e76326 2193 ftpFail(ftpState);
2194 return;
3fdadc70 2195 }
2196}
2197
969c39b9 2198static void
5517260a 2199ftpSendRetr(Ftp::Gateway * ftpState)
969c39b9 2200{
a11382aa 2201 /* check the server control channel is still available */
9e008dda 2202 if (!ftpState || !ftpState->haveControlChannel("ftpSendRetr"))
a11382aa 2203 return;
2204
a689bd4e 2205 debugs(9, 3, HERE);
2206
969c39b9 2207 assert(ftpState->filepath != NULL);
903198a7 2208 snprintf(cbuf, CTRL_BUFLEN, "RETR %s\r\n", ftpState->filepath);
2f47fadf 2209 ftpState->writeCommand(cbuf);
5517260a 2210 ftpState->state = Ftp::Client::SENT_RETR;
969c39b9 2211}
2212
3fdadc70 2213static void
5517260a 2214ftpReadRetr(Ftp::Gateway * ftpState)
3fdadc70 2215{
2216 int code = ftpState->ctrl.replycode;
a689bd4e 2217 debugs(9, 3, HERE);
62e76326 2218
6200d3d6 2219 if (code == 125 || (code == 150 && Comm::IsConnOpen(ftpState->data.conn))) {
62e76326 2220 /* Begin data transfer */
48e7baac 2221 debugs(9, 3, HERE << "begin data transfer from " << ftpState->data.conn->remote << " (" << ftpState->data.conn->local << ")");
6200d3d6 2222 ftpState->switchTimeoutToDataChannel();
5f8252d2 2223 ftpState->maybeReadVirginBody();
5517260a 2224 ftpState->state = Ftp::Client::READING_DATA;
cdc33f35 2225 } else if (code == 150) {
62e76326 2226 /* Accept data channel */
434a79b0 2227 ftpState->listenForDataChannel(ftpState->data.conn);
cdc33f35 2228 } else if (code >= 300) {
62e76326 2229 if (!ftpState->flags.try_slash_hack) {
2230 /* Try this as a directory missing trailing slash... */
6f0aab86 2231 ftpState->hackShortcut(ftpSendCwd);
62e76326 2232 } else {
2233 ftpFail(ftpState);
2234 }
cdc33f35 2235 } else {
62e76326 2236 ftpFail(ftpState);
3fdadc70 2237 }
2238}
2239
0477a072
AJ
2240/**
2241 * Generate the HTTP headers and template fluff around an FTP
2242 * directory listing display.
2243 */
2244void
5517260a 2245Ftp::Gateway::completedListing()
0477a072
AJ
2246{
2247 assert(entry);
5517260a 2248 entry->lock("Ftp::Gateway");
955394ce 2249 ErrorState ferr(ERR_DIR_LISTING, Http::scOkay, request);
913524f0
AJ
2250 ferr.ftp.listing = &listing;
2251 ferr.ftp.cwd_msg = xstrdup(cwd_message.size()? cwd_message.termedBuf() : "");
2252 ferr.ftp.server_msg = ctrl.message;
0477a072 2253 ctrl.message = NULL;
913524f0 2254 entry->replaceHttpReply( ferr.BuildHttpReply() );
0477a072
AJ
2255 EBIT_CLR(entry->flags, ENTRY_FWD_HDR_WAIT);
2256 entry->flush();
5517260a 2257 entry->unlock("Ftp::Gateway");
0477a072
AJ
2258}
2259
3fdadc70 2260static void
5517260a 2261ftpReadTransferDone(Ftp::Gateway * ftpState)
3fdadc70 2262{
2263 int code = ftpState->ctrl.replycode;
a689bd4e 2264 debugs(9, 3, HERE);
62e76326 2265
bbf07a94 2266 if (code == 226 || code == 250) {
62e76326 2267 /* Connection closed; retrieval done. */
0477a072
AJ
2268 if (ftpState->flags.listing) {
2269 ftpState->completedListing();
2270 /* QUIT operation handles sending the reply to client */
2271 }
c0d75e74 2272 ftpSendQuit(ftpState);
f53969cc 2273 } else { /* != 226 */
a689bd4e 2274 debugs(9, DBG_IMPORTANT, HERE << "Got code " << code << " after reading data");
8043ea13 2275 ftpState->failed(ERR_FTP_FAILURE, 0);
6b679a01 2276 /* failed closes ctrl.conn and frees ftpState */
62e76326 2277 return;
3fdadc70 2278 }
2279}
2280
5f8252d2 2281// premature end of the request body
6f0aab86 2282void
5517260a 2283Ftp::Gateway::handleRequestBodyProducerAborted()
3fdadc70 2284{
fccd4a86 2285 Client::handleRequestBodyProducerAborted();
a689bd4e 2286 debugs(9, 3, HERE << "ftpState=" << this);
5f8252d2 2287 failed(ERR_READ_ERROR, 0);
94439e4e 2288}
2289
94439e4e 2290static void
5517260a 2291ftpWriteTransferDone(Ftp::Gateway * ftpState)
94439e4e 2292{
2293 int code = ftpState->ctrl.replycode;
a689bd4e 2294 debugs(9, 3, HERE);
62e76326 2295
bbf07a94 2296 if (!(code == 226 || code == 250)) {
a689bd4e 2297 debugs(9, DBG_IMPORTANT, HERE << "Got code " << code << " after sending data");
8043ea13 2298 ftpState->failed(ERR_FTP_PUT_ERROR, 0);
62e76326 2299 return;
94439e4e 2300 }
62e76326 2301
f53969cc 2302 ftpState->entry->timestampsSet(); /* XXX Is this needed? */
94439e4e 2303 ftpSendReply(ftpState);
969c39b9 2304}
2305
2306static void
5517260a 2307ftpSendQuit(Ftp::Gateway * ftpState)
969c39b9 2308{
a11382aa 2309 /* check the server control channel is still available */
9e008dda 2310 if (!ftpState || !ftpState->haveControlChannel("ftpSendQuit"))
a11382aa 2311 return;
2312
903198a7 2313 snprintf(cbuf, CTRL_BUFLEN, "QUIT\r\n");
2f47fadf 2314 ftpState->writeCommand(cbuf);
5517260a 2315 ftpState->state = Ftp::Client::SENT_QUIT;
3fdadc70 2316}
2317
3cc0f4e7 2318/** Completes a client FTP operation with success or other page
0477a072
AJ
2319 * generated and stored in the entry field by the code issuing QUIT.
2320 */
3fdadc70 2321static void
5517260a 2322ftpReadQuit(Ftp::Gateway * ftpState)
3fdadc70 2323{
5f8252d2 2324 ftpState->serverComplete();
3fdadc70 2325}
2326
969c39b9 2327static void
5517260a 2328ftpTrySlashHack(Ftp::Gateway * ftpState)
969c39b9 2329{
2330 char *path;
e55f0142 2331 ftpState->flags.try_slash_hack = 1;
969c39b9 2332 /* Free old paths */
62e76326 2333
a689bd4e 2334 debugs(9, 3, HERE);
2335
969c39b9 2336 if (ftpState->pathcomps)
62e76326 2337 wordlistDestroy(&ftpState->pathcomps);
2338
969c39b9 2339 safe_free(ftpState->filepath);
62e76326 2340
969c39b9 2341 /* Build the new path (urlpath begins with /) */
3f0e38d6 2342 path = SBufToCstring(ftpState->request->url.path());
62e76326 2343
969c39b9 2344 rfc1738_unescape(path);
62e76326 2345
969c39b9 2346 ftpState->filepath = path;
62e76326 2347
969c39b9 2348 /* And off we go */
dbfed404 2349 ftpGetFile(ftpState);
969c39b9 2350}
2351
63be0a78 2352/**
2353 * Forget hack status. Next error is shown to the user
2354 */
6f0aab86 2355void
5517260a 2356Ftp::Gateway::unhack()
0f169992 2357{
a689bd4e 2358 debugs(9, 3, HERE);
2359
6f0aab86 2360 if (old_request != NULL) {
2361 safe_free(old_request);
2362 safe_free(old_reply);
0f169992 2363 }
2364}
2365
6f0aab86 2366void
5517260a 2367Ftp::Gateway::hackShortcut(FTPSM * nextState)
969c39b9 2368{
c7e0305b 2369 /* Clear some unwanted state */
9fa2e208 2370 setCurrentOffset(0);
6f0aab86 2371 restart_offset = 0;
0f169992 2372 /* Save old error message & some state info */
62e76326 2373
a689bd4e 2374 debugs(9, 3, HERE);
2375
6f0aab86 2376 if (old_request == NULL) {
2377 old_request = ctrl.last_command;
2378 ctrl.last_command = NULL;
2379 old_reply = ctrl.last_reply;
2380 ctrl.last_reply = NULL;
62e76326 2381
6f0aab86 2382 if (pathcomps == NULL && filepath != NULL)
2383 old_filepath = xstrdup(filepath);
0f169992 2384 }
62e76326 2385
969c39b9 2386 /* Jump to the "hack" state */
6f0aab86 2387 nextState(this);
969c39b9 2388}
2389
3fdadc70 2390static void
5517260a 2391ftpFail(Ftp::Gateway *ftpState)
3fdadc70 2392{
51b5dcf5 2393 const bool slashHack = ftpState->request->url.path().caseCmp("/%2f", 4)==0;
cd4d5d45
VL
2394 int code = ftpState->ctrl.replycode;
2395 err_type error_code = ERR_NONE;
2396
2397 debugs(9, 6, "state " << ftpState->state <<
2398 " reply code " << code << "flags(" <<
9e008dda
AJ
2399 (ftpState->flags.isdir?"IS_DIR,":"") <<
2400 (ftpState->flags.try_slash_hack?"TRY_SLASH_HACK":"") << "), " <<
2401 "mdtm=" << ftpState->mdtm << ", size=" << ftpState->theSize <<
51b5dcf5 2402 "slashhack=" << (slashHack? "T":"F"));
62e76326 2403
a689bd4e 2404 /* Try the / hack to support "Netscape" FTP URL's for retreiving files */
f53969cc 2405 if (!ftpState->flags.isdir && /* Not a directory */
51b5dcf5
AJ
2406 !ftpState->flags.try_slash_hack && !slashHack && /* Not doing slash hack */
2407 ftpState->mdtm <= 0 && ftpState->theSize < 0) { /* Not known as a file */
62e76326 2408
2409 switch (ftpState->state) {
2410
5517260a 2411 case Ftp::Client::SENT_CWD:
62e76326 2412
5517260a 2413 case Ftp::Client::SENT_RETR:
62e76326 2414 /* Try the / hack */
6f0aab86 2415 ftpState->hackShortcut(ftpTrySlashHack);
62e76326 2416 return;
2417
2418 default:
2419 break;
2420 }
969c39b9 2421 }
62e76326 2422
cd4d5d45
VL
2423 Http::StatusCode sc = ftpState->failedHttpStatus(error_code);
2424 ErrorState *ftperr = new ErrorState(error_code, sc, ftpState->fwd->request);
2425 ftpState->failed(error_code, code, ftperr);
2426 ftperr->detailError(code);
2427 HttpReply *newrep = ftperr->BuildHttpReply();
2428 delete ftperr;
2429
2430 ftpState->entry->replaceHttpReply(newrep);
2431 ftpSendQuit(ftpState);
9bc73deb 2432}
2433
434a79b0 2434Http::StatusCode
5517260a 2435Ftp::Gateway::failedHttpStatus(err_type &error)
9bc73deb 2436{
434a79b0 2437 if (error == ERR_NONE) {
6f0aab86 2438 switch (state) {
e7ce227f 2439
62e76326 2440 case SENT_USER:
e7ce227f 2441
62e76326 2442 case SENT_PASS:
e7ce227f 2443
434a79b0
DK
2444 if (ctrl.replycode > 500) {
2445 error = ERR_FTP_FORBIDDEN;
2446 return password_url ? Http::scForbidden : Http::scUnauthorized;
2447 } else if (ctrl.replycode == 421) {
2448 error = ERR_FTP_UNAVAILABLE;
2449 return Http::scServiceUnavailable;
2450 }
62e76326 2451 break;
e7ce227f 2452
62e76326 2453 case SENT_CWD:
e7ce227f 2454
62e76326 2455 case SENT_RETR:
434a79b0
DK
2456 if (ctrl.replycode == 550) {
2457 error = ERR_FTP_NOT_FOUND;
2458 return Http::scNotFound;
2459 }
62e76326 2460 break;
e7ce227f 2461
62e76326 2462 default:
2463 break;
2464 }
b6a2f15e 2465 }
5517260a 2466 return Ftp::Client::failedHttpStatus(error);
3fdadc70 2467}
2468
54220df8 2469static void
5517260a 2470ftpSendReply(Ftp::Gateway * ftpState)
54220df8 2471{
acce49bf 2472 int code = ftpState->ctrl.replycode;
955394ce 2473 Http::StatusCode http_code;
728da2ee 2474 err_type err_code = ERR_NONE;
a11382aa 2475
a689bd4e 2476 debugs(9, 3, HERE << ftpState->entry->url() << ", code " << code);
62e76326 2477
fa80a8ef 2478 if (cbdataReferenceValid(ftpState))
a689bd4e 2479 debugs(9, 5, HERE << "ftpState (" << ftpState << ") is valid!");
62e76326 2480
bbf07a94 2481 if (code == 226 || code == 250) {
62e76326 2482 err_code = (ftpState->mdtm > 0) ? ERR_FTP_PUT_MODIFIED : ERR_FTP_PUT_CREATED;
955394ce 2483 http_code = (ftpState->mdtm > 0) ? Http::scAccepted : Http::scCreated;
9bc73deb 2484 } else if (code == 227) {
62e76326 2485 err_code = ERR_FTP_PUT_CREATED;
955394ce 2486 http_code = Http::scCreated;
54220df8 2487 } else {
62e76326 2488 err_code = ERR_FTP_PUT_ERROR;
955394ce 2489 http_code = Http::scInternalServerError;
54220df8 2490 }
62e76326 2491
913524f0 2492 ErrorState err(err_code, http_code, ftpState->request);
62e76326 2493
54220df8 2494 if (ftpState->old_request)
913524f0 2495 err.ftp.request = xstrdup(ftpState->old_request);
54220df8 2496 else
913524f0 2497 err.ftp.request = xstrdup(ftpState->ctrl.last_command);
62e76326 2498
54220df8 2499 if (ftpState->old_reply)
913524f0 2500 err.ftp.reply = xstrdup(ftpState->old_reply);
0dfb298f 2501 else if (ftpState->ctrl.last_reply)
913524f0 2502 err.ftp.reply = xstrdup(ftpState->ctrl.last_reply);
0dfb298f 2503 else
913524f0 2504 err.ftp.reply = xstrdup("");
62e76326 2505
129fe2a1
CT
2506 // TODO: interpret as FTP-specific error code
2507 err.detailError(code);
2508
913524f0 2509 ftpState->entry->replaceHttpReply( err.BuildHttpReply() );
62e76326 2510
9bc73deb 2511 ftpSendQuit(ftpState);
54220df8 2512}
2513
6f0aab86 2514void
5517260a 2515Ftp::Gateway::appendSuccessHeader()
3fdadc70 2516{
a689bd4e 2517 debugs(9, 3, HERE);
ee65546f 2518
6f0aab86 2519 if (flags.http_header_sent)
62e76326 2520 return;
2521
585ab260 2522 HttpReply *reply = new HttpReply;
2523
6f0aab86 2524 flags.http_header_sent = 1;
62e76326 2525
0477a072 2526 assert(entry->isEmpty());
62e76326 2527
0477a072 2528 EBIT_CLR(entry->flags, ENTRY_FWD_HDR_WAIT);
62e76326 2529
f53969cc 2530 entry->buffer(); /* released when done processing current data payload */
b66315e4 2531
51b5dcf5
AJ
2532 SBuf urlPath = request->url.path();
2533 auto t = urlPath.rfind('/');
2534 SBuf filename = urlPath.substr(t != SBuf::npos ? t : 0);
2535
2536 const char *mime_type = NULL;
2537 const char *mime_enc = NULL;
62e76326 2538
6f0aab86 2539 if (flags.isdir) {
62e76326 2540 mime_type = "text/html";
3fdadc70 2541 } else {
6f0aab86 2542 switch (typecode) {
62e76326 2543
2544 case 'I':
2545 mime_type = "application/octet-stream";
51b5dcf5
AJ
2546 // XXX: performance regression, c_str() may reallocate
2547 mime_enc = mimeGetContentEncoding(filename.c_str());
62e76326 2548 break;
2549
2550 case 'A':
2551 mime_type = "text/plain";
2552 break;
2553
2554 default:
51b5dcf5
AJ
2555 // XXX: performance regression, c_str() may reallocate
2556 mime_type = mimeGetContentType(filename.c_str());
2557 mime_enc = mimeGetContentEncoding(filename.c_str());
62e76326 2558 break;
2559 }
3fdadc70 2560 }
62e76326 2561
cb69b4c7 2562 /* set standard stuff */
62e76326 2563
9fa2e208 2564 if (0 == getCurrentOffset()) {
690b22c6 2565 /* Full reply */
955394ce 2566 reply->setHeaders(Http::scOkay, "Gatewaying", mime_type, theSize, mdtm, -2);
9fa2e208 2567 } else if (theSize < getCurrentOffset()) {
9e008dda
AJ
2568 /*
2569 * DPW 2007-05-04
2570 * offset should not be larger than theSize. We should
2571 * not be seeing this condition any more because we'll only
2572 * send REST if we know the theSize and if it is less than theSize.
2573 */
2574 debugs(0,DBG_CRITICAL,HERE << "Whoops! " <<
9fa2e208 2575 " current offset=" << getCurrentOffset() <<
9e008dda
AJ
2576 ", but theSize=" << theSize <<
2577 ". assuming full content response");
955394ce 2578 reply->setHeaders(Http::scOkay, "Gatewaying", mime_type, theSize, mdtm, -2);
690b22c6 2579 } else {
62e76326 2580 /* Partial reply */
2581 HttpHdrRangeSpec range_spec;
9fa2e208
CT
2582 range_spec.offset = getCurrentOffset();
2583 range_spec.length = theSize - getCurrentOffset();
955394ce 2584 reply->setHeaders(Http::scPartialContent, "Gatewaying", mime_type, theSize - getCurrentOffset(), mdtm, -2);
47f6e231 2585 httpHeaderAddContRange(&reply->header, range_spec, theSize);
cfbf5373 2586 }
62e76326 2587
cb69b4c7 2588 /* additional info */
2589 if (mime_enc)
789217a2 2590 reply->header.putStr(Http::HdrType::CONTENT_ENCODING, mime_enc);
62e76326 2591
585ab260 2592 setVirginReply(reply);
2593 adaptOrFinalizeReply();
063a47b5 2594}
2595
2596void
5517260a 2597Ftp::Gateway::haveParsedReplyHeaders()
063a47b5 2598{
fccd4a86 2599 Client::haveParsedReplyHeaders();
c1520b67 2600
063a47b5 2601 StoreEntry *e = entry;
62e76326 2602
3900307b 2603 e->timestampsSet();
62e76326 2604
6f0aab86 2605 if (flags.authenticated) {
62e76326 2606 /*
2607 * Authenticated requests can't be cached.
2608 */
5f33b71d 2609 e->release();
6919be24 2610 } else if (!EBIT_TEST(e->flags, RELEASE_REQUEST) && !getCurrentOffset()) {
d88e3c49 2611 e->setPublicKey();
c68e9c6b 2612 } else {
5f33b71d 2613 e->release();
c68e9c6b 2614 }
77a30ebb 2615}
bfcaf585 2616
6f0aab86 2617HttpReply *
5517260a 2618Ftp::Gateway::ftpAuthRequired(HttpRequest * request, const char *realm)
cb69b4c7 2619{
955394ce 2620 ErrorState err(ERR_CACHE_ACCESS_DENIED, Http::scUnauthorized, request);
913524f0 2621 HttpReply *newrep = err.BuildHttpReply();
9da6b594 2622#if HAVE_AUTH_MODULE_BASIC
63259c34 2623 /* add Authenticate header */
a9925b40 2624 newrep->header.putAuth("Basic", realm);
9da6b594 2625#endif
4a56ee8d 2626 return newrep;
cb69b4c7 2627}
8f872bb6 2628
851feda6 2629const SBuf &
5517260a 2630Ftp::UrlWith2f(HttpRequest * request)
8f872bb6 2631{
51b5dcf5 2632 SBuf newbuf("%2f");
62e76326 2633
851feda6
AJ
2634 if (request->url.getScheme() != AnyP::PROTO_FTP) {
2635 static const SBuf nil;
2636 return nil;
2637 }
62e76326 2638
51b5dcf5
AJ
2639 if (request->url.path()[0] == '/') {
2640 newbuf.append(request->url.path());
2641 request->url.path(newbuf);
51b5dcf5
AJ
2642 } else if (!request->url.path().startsWith(newbuf)) {
2643 newbuf.append(request->url.path().substr(1));
2644 request->url.path(newbuf);
8f872bb6 2645 }
62e76326 2646
851feda6 2647 return request->effectiveRequestUri();
8f872bb6 2648}
253caccb 2649
2650void
5517260a 2651Ftp::Gateway::printfReplyBody(const char *fmt, ...)
253caccb 2652{
2653 va_list args;
2654 va_start (args, fmt);
2655 static char buf[4096];
2656 buf[0] = '\0';
2657 vsnprintf(buf, 4096, fmt, args);
2658 writeReplyBody(buf, strlen(buf));
810635e3 2659 va_end(args);
253caccb 2660}
2661
63be0a78 2662/**
253caccb 2663 * Call this when there is data from the origin server
2664 * which should be sent to either StoreEntry, or to ICAP...
2665 */
2666void
5517260a 2667Ftp::Gateway::writeReplyBody(const char *dataToWrite, size_t dataLength)
253caccb 2668{
e053c141
FC
2669 debugs(9, 5, HERE << "writing " << dataLength << " bytes to the reply");
2670 addVirginReplyBody(dataToWrite, dataLength);
253caccb 2671}
2672
63be0a78 2673/**
2674 * A hack to ensure we do not double-complete on the forward entry.
2675 *
5517260a 2676 \todo Ftp::Gateway logic should probably be rewritten to avoid
f53969cc 2677 * double-completion or FwdState should be rewritten to allow it.
63be0a78 2678 */
16846d00 2679void
5517260a 2680Ftp::Gateway::completeForwarding()
16846d00 2681{
5f8252d2 2682 if (fwd == NULL || flags.completed_forwarding) {
a689bd4e 2683 debugs(9, 3, HERE << "completeForwarding avoids " <<
6b679a01 2684 "double-complete on FD " << ctrl.conn->fd << ", Data FD " << data.conn->fd <<
9e008dda 2685 ", this " << this << ", fwd " << fwd);
5f8252d2 2686 return;
2687 }
16846d00 2688
5f8252d2 2689 flags.completed_forwarding = true;
fccd4a86 2690 Client::completeForwarding();
16846d00 2691}
2692
63be0a78 2693/**
2694 * Have we lost the FTP server control channel?
2695 *
f53969cc
SM
2696 \retval true The server control channel is available.
2697 \retval false The server control channel is not available.
63be0a78 2698 */
a11382aa 2699bool
5517260a 2700Ftp::Gateway::haveControlChannel(const char *caller_name) const
a11382aa 2701{
9e008dda 2702 if (doneWithServer())
33ed5343 2703 return false;
a11382aa 2704
33ed5343 2705 /* doneWithServer() only checks BOTH channels are closed. */
6200d3d6 2706 if (!Comm::IsConnOpen(ctrl.conn)) {
a689bd4e 2707 debugs(9, DBG_IMPORTANT, "WARNING! FTP Server Control channel is closed, but Data channel still active.");
33ed5343 2708 debugs(9, 2, caller_name << ": attempted on a closed FTP channel.");
2709 return false;
2710 }
2711
2712 return true;
a11382aa 2713}
5517260a 2714
aea65fec
AR
2715bool
2716Ftp::Gateway::mayReadVirginReplyBody() const
2717{
2718 // TODO: Can we do what Ftp::Relay::mayReadVirginReplyBody() does instead?
2719 return !doneWithServer();
2720}
2721
5517260a
AR
2722AsyncJob::Pointer
2723Ftp::StartGateway(FwdState *const fwdState)
2724{
2725 return AsyncJob::Start(new Ftp::Gateway(fwdState));
2726}
f53969cc 2727