]> git.saurik.com Git - apt.git/blame - methods/ftp.cc
methods/ftp: Cope with weird PASV responses
[apt.git] / methods / ftp.cc
CommitLineData
30b30ec1
AL
1// -*- mode: cpp; mode: fold -*-
2// Description /*{{{*/
7db98ffc 3// $Id: ftp.cc,v 1.31.2.1 2004/01/16 18:58:50 mdz Exp $
30b30ec1
AL
4/* ######################################################################
5
1e3f4083 6 FTP Acquire Method - This is the FTP acquire method for APT.
30b30ec1
AL
7
8 This is a very simple implementation that does not try to optimize
9 at all. Commands are sent syncronously with the FTP server (as the
10 rfc recommends, but it is not really necessary..) and no tricks are
11 done to speed things along.
934b6582
AL
12
13 RFC 2428 describes the IPv6 FTP behavior
14
30b30ec1
AL
15 ##################################################################### */
16 /*}}}*/
17// Include Files /*{{{*/
ea542140
DK
18#include <config.h>
19
30b30ec1 20#include <apt-pkg/fileutl.h>
30b30ec1 21#include <apt-pkg/error.h>
63b1700f 22#include <apt-pkg/hashes.h>
f1c081b6 23#include <apt-pkg/netrc.h>
472ff00e 24#include <apt-pkg/configuration.h>
453b82a3 25#include <apt-pkg/strutl.h>
30b30ec1 26
453b82a3
DK
27#include <ctype.h>
28#include <stdlib.h>
29#include <string.h>
30b30ec1
AL
30#include <sys/stat.h>
31#include <sys/time.h>
30b30ec1
AL
32#include <unistd.h>
33#include <signal.h>
34#include <stdio.h>
35#include <errno.h>
36#include <stdarg.h>
076cc664 37#include <iostream>
30b30ec1
AL
38
39// Internet stuff
40#include <netinet/in.h>
30b30ec1
AL
41#include <arpa/inet.h>
42#include <netdb.h>
43
6d13bbca 44#include "rfc2553emu.h"
0837bd25 45#include "connect.h"
ce0ae89a 46#include "ftp.h"
453b82a3 47
ea542140 48#include <apti18n.h>
30b30ec1
AL
49 /*}}}*/
50
076cc664
AL
51using namespace std;
52
b2e465d6
AL
53/* This table is for the EPRT and EPSV commands, it maps the OS address
54 family to the IETF address families */
55struct AFMap
56{
57 unsigned long Family;
58 unsigned long IETFFamily;
59};
60
61#ifndef AF_INET6
67c3067f 62struct AFMap AFMap[] = {{AF_INET,1},{0, 0}};
b2e465d6 63#else
67c3067f 64struct AFMap AFMap[] = {{AF_INET,1},{AF_INET6,2},{0, 0}};
b2e465d6
AL
65#endif
66
30b30ec1
AL
67unsigned long TimeOut = 120;
68URI Proxy;
ce0ae89a
AL
69string FtpMethod::FailFile;
70int FtpMethod::FailFd = -1;
71time_t FtpMethod::FailTime = 0;
30b30ec1
AL
72
73// FTPConn::FTPConn - Constructor /*{{{*/
74// ---------------------------------------------------------------------
75/* */
25613a61 76FTPConn::FTPConn(URI Srv) : Len(0), ServerFd(-1), DataFd(-1),
f5a34606 77 DataListenFd(-1), ServerName(Srv),
25613a61
DK
78 ForceExtended(false), TryPassive(true),
79 PeerAddrLen(0), ServerAddrLen(0)
30b30ec1 80{
ce0ae89a 81 Debug = _config->FindB("Debug::Acquire::Ftp",false);
b2e465d6 82 PasvAddr = 0;
dcaa1185 83 Buffer[0] = '\0';
30b30ec1
AL
84}
85 /*}}}*/
86// FTPConn::~FTPConn - Destructor /*{{{*/
87// ---------------------------------------------------------------------
88/* */
89FTPConn::~FTPConn()
90{
91 Close();
92}
93 /*}}}*/
94// FTPConn::Close - Close down the connection /*{{{*/
95// ---------------------------------------------------------------------
96/* Just tear down the socket and data socket */
97void FTPConn::Close()
98{
99 close(ServerFd);
100 ServerFd = -1;
101 close(DataFd);
102 DataFd = -1;
103 close(DataListenFd);
104 DataListenFd = -1;
b2e465d6
AL
105
106 if (PasvAddr != 0)
107 freeaddrinfo(PasvAddr);
108 PasvAddr = 0;
30b30ec1
AL
109}
110 /*}}}*/
111// FTPConn::Open - Open a new connection /*{{{*/
112// ---------------------------------------------------------------------
113/* Connect to the server using a non-blocking connection and perform a
114 login. */
ce0ae89a 115bool FTPConn::Open(pkgAcqMethod *Owner)
30b30ec1
AL
116{
117 // Use the already open connection if possible.
118 if (ServerFd != -1)
119 return true;
120
121 Close();
b2e465d6 122
30b30ec1 123 // Determine the proxy setting
788a8f42
EL
124 string SpecificProxy = _config->Find("Acquire::ftp::Proxy::" + ServerName.Host);
125 if (!SpecificProxy.empty())
30b30ec1 126 {
788a8f42
EL
127 if (SpecificProxy == "DIRECT")
128 Proxy = "";
129 else
130 Proxy = SpecificProxy;
30b30ec1
AL
131 }
132 else
788a8f42
EL
133 {
134 string DefProxy = _config->Find("Acquire::ftp::Proxy");
135 if (!DefProxy.empty())
136 {
137 Proxy = DefProxy;
138 }
139 else
140 {
141 char* result = getenv("ftp_proxy");
142 Proxy = result ? result : "";
143 }
144 }
145
f8081133
AL
146 // Parse no_proxy, a , separated list of domains
147 if (getenv("no_proxy") != 0)
148 {
149 if (CheckDomainList(ServerName.Host,getenv("no_proxy")) == true)
150 Proxy = "";
151 }
152
30b30ec1 153 // Determine what host and port to use based on the proxy settings
6d13bbca 154 int Port = 0;
30b30ec1
AL
155 string Host;
156 if (Proxy.empty() == true)
157 {
158 if (ServerName.Port != 0)
159 Port = ServerName.Port;
30b30ec1
AL
160 Host = ServerName.Host;
161 }
162 else
163 {
164 if (Proxy.Port != 0)
165 Port = Proxy.Port;
166 Host = Proxy.Host;
167 }
6d13bbca 168
b2e465d6
AL
169 /* Connect to the remote server. Since FTP is connection oriented we
170 want to make sure we get a new server every time we reconnect */
171 RotateDNS();
9505213b 172 if (Connect(Host,Port,"ftp",21,ServerFd,TimeOut,Owner) == false)
0837bd25 173 return false;
c968dc2f
AL
174
175 // Login must be before getpeername otherwise dante won't work.
dc738e7a 176 Owner->Status(_("Logging in"));
c968dc2f 177 bool Res = Login();
b2e465d6
AL
178
179 // Get the remote server's address
180 PeerAddrLen = sizeof(PeerAddr);
181 if (getpeername(ServerFd,(sockaddr *)&PeerAddr,&PeerAddrLen) != 0)
dc738e7a 182 return _error->Errno("getpeername",_("Unable to determine the peer name"));
30b30ec1 183
b2e465d6
AL
184 // Get the local machine's address
185 ServerAddrLen = sizeof(ServerAddr);
186 if (getsockname(ServerFd,(sockaddr *)&ServerAddr,&ServerAddrLen) != 0)
dc738e7a 187 return _error->Errno("getsockname",_("Unable to determine the local name"));
b2e465d6 188
c968dc2f 189 return Res;
30b30ec1
AL
190}
191 /*}}}*/
192// FTPConn::Login - Login to the remote server /*{{{*/
193// ---------------------------------------------------------------------
194/* This performs both normal login and proxy login using a simples script
195 stored in the config file. */
196bool FTPConn::Login()
197{
198 unsigned int Tag;
199 string Msg;
200
201 // Setup the variables needed for authentication
202 string User = "anonymous";
44a38e53 203 string Pass = "apt_get_ftp_2.1@debian.linux.user";
30b30ec1
AL
204
205 // Fill in the user/pass
206 if (ServerName.User.empty() == false)
207 User = ServerName.User;
208 if (ServerName.Password.empty() == false)
209 Pass = ServerName.Password;
210
211 // Perform simple login
212 if (Proxy.empty() == true)
213 {
214 // Read the initial response
215 if (ReadResp(Tag,Msg) == false)
216 return false;
217 if (Tag >= 400)
db0db9fe 218 return _error->Error(_("The server refused the connection and said: %s"),Msg.c_str());
30b30ec1
AL
219
220 // Send the user
221 if (WriteMsg(Tag,Msg,"USER %s",User.c_str()) == false)
222 return false;
223 if (Tag >= 400)
dc738e7a 224 return _error->Error(_("USER failed, server said: %s"),Msg.c_str());
30b30ec1 225
2391e7b5
AL
226 if (Tag == 331) { // 331 User name okay, need password.
227 // Send the Password
228 if (WriteMsg(Tag,Msg,"PASS %s",Pass.c_str()) == false)
229 return false;
230 if (Tag >= 400)
231 return _error->Error(_("PASS failed, server said: %s"),Msg.c_str());
232 }
30b30ec1
AL
233
234 // Enter passive mode
10861bb5
AL
235 if (_config->Exists("Acquire::FTP::Passive::" + ServerName.Host) == true)
236 TryPassive = _config->FindB("Acquire::FTP::Passive::" + ServerName.Host,true);
30b30ec1 237 else
b2e465d6 238 TryPassive = _config->FindB("Acquire::FTP::Passive",true);
30b30ec1
AL
239 }
240 else
241 {
242 // Read the initial response
243 if (ReadResp(Tag,Msg) == false)
244 return false;
245 if (Tag >= 400)
1169dbfa 246 return _error->Error(_("The server refused the connection and said: %s"),Msg.c_str());
30b30ec1
AL
247
248 // Perform proxy script execution
249 Configuration::Item const *Opts = _config->Tree("Acquire::ftp::ProxyLogin");
250 if (Opts == 0 || Opts->Child == 0)
dc738e7a
AL
251 return _error->Error(_("A proxy server was specified but no login "
252 "script, Acquire::ftp::ProxyLogin is empty."));
30b30ec1
AL
253 Opts = Opts->Child;
254
255 // Iterate over the entire login script
256 for (; Opts != 0; Opts = Opts->Next)
257 {
258 if (Opts->Value.empty() == true)
259 continue;
b8eba208 260
30b30ec1 261 // Substitute the variables into the command
30b30ec1
AL
262 string Tmp = Opts->Value;
263 Tmp = SubstVar(Tmp,"$(PROXY_USER)",Proxy.User);
264 Tmp = SubstVar(Tmp,"$(PROXY_PASS)",Proxy.Password);
265 Tmp = SubstVar(Tmp,"$(SITE_USER)",User);
266 Tmp = SubstVar(Tmp,"$(SITE_PASS)",Pass);
b8eba208
DK
267 if (ServerName.Port != 0)
268 {
269 std::string SitePort;
270 strprintf(SitePort, "%u", ServerName.Port);
271 Tmp = SubstVar(Tmp,"$(SITE_PORT)", SitePort);
272 }
273 else
274 Tmp = SubstVar(Tmp,"$(SITE_PORT)", "21");
30b30ec1
AL
275 Tmp = SubstVar(Tmp,"$(SITE)",ServerName.Host);
276
277 // Send the command
278 if (WriteMsg(Tag,Msg,"%s",Tmp.c_str()) == false)
279 return false;
280 if (Tag >= 400)
dc738e7a 281 return _error->Error(_("Login script command '%s' failed, server said: %s"),Tmp.c_str(),Msg.c_str());
30b30ec1
AL
282 }
283
284 // Enter passive mode
285 TryPassive = false;
10861bb5
AL
286 if (_config->Exists("Acquire::FTP::Passive::" + ServerName.Host) == true)
287 TryPassive = _config->FindB("Acquire::FTP::Passive::" + ServerName.Host,true);
30b30ec1
AL
288 else
289 {
10861bb5
AL
290 if (_config->Exists("Acquire::FTP::Proxy::Passive") == true)
291 TryPassive = _config->FindB("Acquire::FTP::Proxy::Passive",true);
30b30ec1 292 else
10861bb5 293 TryPassive = _config->FindB("Acquire::FTP::Passive",true);
30b30ec1
AL
294 }
295 }
296
b2e465d6
AL
297 // Force the use of extended commands
298 if (_config->Exists("Acquire::FTP::ForceExtended::" + ServerName.Host) == true)
299 ForceExtended = _config->FindB("Acquire::FTP::ForceExtended::" + ServerName.Host,true);
300 else
301 ForceExtended = _config->FindB("Acquire::FTP::ForceExtended",false);
302
30b30ec1
AL
303 // Binary mode
304 if (WriteMsg(Tag,Msg,"TYPE I") == false)
305 return false;
306 if (Tag >= 400)
dc738e7a 307 return _error->Error(_("TYPE failed, server said: %s"),Msg.c_str());
30b30ec1
AL
308
309 return true;
310}
311 /*}}}*/
312// FTPConn::ReadLine - Read a line from the server /*{{{*/
313// ---------------------------------------------------------------------
314/* This performs a very simple buffered read. */
315bool FTPConn::ReadLine(string &Text)
316{
2e90f6e0
AL
317 if (ServerFd == -1)
318 return false;
319
30b30ec1
AL
320 // Suck in a line
321 while (Len < sizeof(Buffer))
322 {
323 // Scan the buffer for a new line
324 for (unsigned int I = 0; I != Len; I++)
325 {
326 // Escape some special chars
327 if (Buffer[I] == 0)
328 Buffer[I] = '?';
329
330 // End of line?
331 if (Buffer[I] != '\n')
332 continue;
333
334 I++;
335 Text = string(Buffer,I);
336 memmove(Buffer,Buffer+I,Len - I);
337 Len -= I;
338 return true;
339 }
340
341 // Wait for some data..
342 if (WaitFd(ServerFd,false,TimeOut) == false)
ce0ae89a
AL
343 {
344 Close();
dc738e7a 345 return _error->Error(_("Connection timeout"));
ce0ae89a 346 }
30b30ec1
AL
347
348 // Suck it back
2e90f6e0 349 int Res = read(ServerFd,Buffer + Len,sizeof(Buffer) - Len);
b2e465d6 350 if (Res == 0)
dc738e7a 351 _error->Error(_("Server closed the connection"));
30b30ec1 352 if (Res <= 0)
ce0ae89a 353 {
dc738e7a 354 _error->Errno("read",_("Read error"));
ce0ae89a 355 Close();
f93d1355 356 return false;
ce0ae89a 357 }
30b30ec1
AL
358 Len += Res;
359 }
360
dc738e7a 361 return _error->Error(_("A response overflowed the buffer."));
30b30ec1
AL
362}
363 /*}}}*/
364// FTPConn::ReadResp - Read a full response from the server /*{{{*/
365// ---------------------------------------------------------------------
366/* This reads a reply code from the server, it handles both p */
367bool FTPConn::ReadResp(unsigned int &Ret,string &Text)
368{
369 // Grab the first line of the response
370 string Msg;
371 if (ReadLine(Msg) == false)
372 return false;
373
374 // Get the ID code
375 char *End;
376 Ret = strtol(Msg.c_str(),&End,10);
377 if (End - Msg.c_str() != 3)
dc738e7a 378 return _error->Error(_("Protocol corruption"));
30b30ec1
AL
379
380 // All done ?
381 Text = Msg.c_str()+4;
382 if (*End == ' ')
383 {
384 if (Debug == true)
ce0ae89a 385 cerr << "<- '" << QuoteString(Text,"") << "'" << endl;
30b30ec1
AL
386 return true;
387 }
388
389 if (*End != '-')
dc738e7a 390 return _error->Error(_("Protocol corruption"));
30b30ec1
AL
391
392 /* Okay, here we do the continued message trick. This is foolish, but
393 proftpd follows the protocol as specified and wu-ftpd doesn't, so
394 we filter. I wonder how many clients break if you use proftpd and
395 put a '- in the 3rd spot in the message? */
396 char Leader[4];
397 strncpy(Leader,Msg.c_str(),3);
398 Leader[3] = 0;
399 while (ReadLine(Msg) == true)
400 {
401 // Short, it must be using RFC continuation..
402 if (Msg.length() < 4)
403 {
404 Text += Msg;
405 continue;
406 }
407
408 // Oops, finished
409 if (strncmp(Msg.c_str(),Leader,3) == 0 && Msg[3] == ' ')
410 {
411 Text += Msg.c_str()+4;
412 break;
413 }
414
415 // This message has the wu-ftpd style reply code prefixed
416 if (strncmp(Msg.c_str(),Leader,3) == 0 && Msg[3] == '-')
417 {
418 Text += Msg.c_str()+4;
419 continue;
420 }
421
422 // Must be RFC style prefixing
423 Text += Msg;
424 }
425
426 if (Debug == true && _error->PendingError() == false)
ce0ae89a 427 cerr << "<- '" << QuoteString(Text,"") << "'" << endl;
30b30ec1
AL
428
429 return !_error->PendingError();
430}
431 /*}}}*/
432// FTPConn::WriteMsg - Send a message to the server /*{{{*/
433// ---------------------------------------------------------------------
434/* Simple printf like function.. */
435bool FTPConn::WriteMsg(unsigned int &Ret,string &Text,const char *Fmt,...)
436{
437 va_list args;
438 va_start(args,Fmt);
439
440 // sprintf the description
441 char S[400];
442 vsnprintf(S,sizeof(S) - 4,Fmt,args);
443 strcat(S,"\r\n");
11d0fb91 444 va_end(args);
30b30ec1
AL
445
446 if (Debug == true)
ce0ae89a 447 cerr << "-> '" << QuoteString(S,"") << "'" << endl;
30b30ec1
AL
448
449 // Send it off
450 unsigned long Len = strlen(S);
451 unsigned long Start = 0;
452 while (Len != 0)
453 {
454 if (WaitFd(ServerFd,true,TimeOut) == false)
ce0ae89a
AL
455 {
456 Close();
dc738e7a 457 return _error->Error(_("Connection timeout"));
ce0ae89a 458 }
30b30ec1
AL
459
460 int Res = write(ServerFd,S + Start,Len);
461 if (Res <= 0)
ce0ae89a 462 {
db0db9fe 463 _error->Errno("write",_("Write error"));
ce0ae89a 464 Close();
f93d1355 465 return false;
ce0ae89a
AL
466 }
467
30b30ec1
AL
468 Len -= Res;
469 Start += Res;
470 }
471
472 return ReadResp(Ret,Text);
473}
474 /*}}}*/
475// FTPConn::GoPasv - Enter Passive mode /*{{{*/
476// ---------------------------------------------------------------------
477/* Try to enter passive mode, the return code does not indicate if passive
478 mode could or could not be established, only if there was a fatal error.
b2e465d6 479 We have to enter passive mode every time we make a data connection :| */
30b30ec1
AL
480bool FTPConn::GoPasv()
481{
b2e465d6
AL
482 /* The PASV command only works on IPv4 sockets, even though it could
483 in theory suppory IPv6 via an all zeros reply */
484 if (((struct sockaddr *)&PeerAddr)->sa_family != AF_INET ||
485 ForceExtended == true)
486 return ExtGoPasv();
487
488 if (PasvAddr != 0)
489 freeaddrinfo(PasvAddr);
490 PasvAddr = 0;
491
30b30ec1
AL
492 // Try to enable pasv mode
493 unsigned int Tag;
494 string Msg;
495 if (WriteMsg(Tag,Msg,"PASV") == false)
496 return false;
497
498 // Unsupported function
499 string::size_type Pos = Msg.find('(');
25a69416
JAK
500 if (Tag >= 400)
501 return true;
502
503 //wu-2.6.2(1) ftp server, returns
504 //227 Entering Passive Mode 193,219,28,140,150,111
505 //without parentheses, let's try to cope with it.
506 //wget(1) and ftp(1) can.
507 if (Pos == string::npos)
508 Pos = Msg.rfind(' ');
509 else
510 ++Pos;
511
512 // Still unsupported function
513 if (Pos == string::npos)
30b30ec1 514 return true;
30b30ec1
AL
515
516 // Scan it
517 unsigned a0,a1,a2,a3,p0,p1;
25a69416 518 if (sscanf(Msg.c_str() + Pos,"%u,%u,%u,%u,%u,%u",&a0,&a1,&a2,&a3,&p0,&p1) != 6)
b2e465d6
AL
519 return true;
520
521 /* Some evil servers return 0 to mean their addr. We can actually speak
522 to these servers natively using IPv6 */
523 if (a0 == 0 && a1 == 0 && a2 == 0 && a3 == 0)
30b30ec1 524 {
b2e465d6
AL
525 // Get the IP in text form
526 char Name[NI_MAXHOST];
527 char Service[NI_MAXSERV];
528 getnameinfo((struct sockaddr *)&PeerAddr,PeerAddrLen,
529 Name,sizeof(Name),Service,sizeof(Service),
530 NI_NUMERICHOST|NI_NUMERICSERV);
531
532 struct addrinfo Hints;
533 memset(&Hints,0,sizeof(Hints));
534 Hints.ai_socktype = SOCK_STREAM;
535 Hints.ai_family = ((struct sockaddr *)&PeerAddr)->sa_family;
536 Hints.ai_flags |= AI_NUMERICHOST;
537
538 // Get a new passive address.
539 char Port[100];
540 snprintf(Port,sizeof(Port),"%u",(p0 << 8) + p1);
541 if (getaddrinfo(Name,Port,&Hints,&PasvAddr) != 0)
542 return true;
30b30ec1
AL
543 return true;
544 }
545
b2e465d6
AL
546 struct addrinfo Hints;
547 memset(&Hints,0,sizeof(Hints));
548 Hints.ai_socktype = SOCK_STREAM;
549 Hints.ai_family = AF_INET;
550 Hints.ai_flags |= AI_NUMERICHOST;
30b30ec1 551
b2e465d6
AL
552 // Get a new passive address.
553 char Port[100];
554 snprintf(Port,sizeof(Port),"%u",(p0 << 8) + p1);
555 char Name[100];
556 snprintf(Name,sizeof(Name),"%u.%u.%u.%u",a0,a1,a2,a3);
557 if (getaddrinfo(Name,Port,&Hints,&PasvAddr) != 0)
558 return true;
559 return true;
560}
561 /*}}}*/
562// FTPConn::ExtGoPasv - Enter Extended Passive mode /*{{{*/
563// ---------------------------------------------------------------------
564/* Try to enter extended passive mode. See GoPasv above and RFC 2428 */
565bool FTPConn::ExtGoPasv()
566{
567 if (PasvAddr != 0)
568 freeaddrinfo(PasvAddr);
569 PasvAddr = 0;
570
571 // Try to enable pasv mode
572 unsigned int Tag;
573 string Msg;
574 if (WriteMsg(Tag,Msg,"EPSV") == false)
575 return false;
576
577 // Unsupported function
578 string::size_type Pos = Msg.find('(');
579 if (Tag >= 400 || Pos == string::npos)
580 return true;
581
582 // Scan it
583 string::const_iterator List[4];
584 unsigned Count = 0;
585 Pos++;
f7f0d6c7 586 for (string::const_iterator I = Msg.begin() + Pos; I < Msg.end(); ++I)
b2e465d6
AL
587 {
588 if (*I != Msg[Pos])
589 continue;
590 if (Count >= 4)
591 return true;
592 List[Count++] = I;
593 }
594 if (Count != 4)
595 return true;
596
597 // Break it up ..
598 unsigned long Proto = 0;
599 unsigned long Port = 0;
600 string IP;
601 IP = string(List[1]+1,List[2]);
602 Port = atoi(string(List[2]+1,List[3]).c_str());
603 if (IP.empty() == false)
604 Proto = atoi(string(List[0]+1,List[1]).c_str());
605
606 if (Port == 0)
607 return false;
608
609 // String version of the port
610 char PStr[100];
611 snprintf(PStr,sizeof(PStr),"%lu",Port);
612
613 // Get the IP in text form
614 struct addrinfo Hints;
615 memset(&Hints,0,sizeof(Hints));
616 Hints.ai_socktype = SOCK_STREAM;
617 Hints.ai_flags |= AI_NUMERICHOST;
618
619 /* The RFC defined case, connect to the old IP/protocol using the
620 new port. */
621 if (IP.empty() == true)
30b30ec1 622 {
b2e465d6
AL
623 // Get the IP in text form
624 char Name[NI_MAXHOST];
625 char Service[NI_MAXSERV];
626 getnameinfo((struct sockaddr *)&PeerAddr,PeerAddrLen,
627 Name,sizeof(Name),Service,sizeof(Service),
628 NI_NUMERICHOST|NI_NUMERICSERV);
629 IP = Name;
630 Hints.ai_family = ((struct sockaddr *)&PeerAddr)->sa_family;
30b30ec1
AL
631 }
632 else
633 {
b2e465d6
AL
634 // Get the family..
635 Hints.ai_family = 0;
636 for (unsigned J = 0; AFMap[J].Family != 0; J++)
637 if (AFMap[J].IETFFamily == Proto)
638 Hints.ai_family = AFMap[J].Family;
639 if (Hints.ai_family == 0)
640 return true;
30b30ec1
AL
641 }
642
b2e465d6 643 // Get a new passive address.
cddbc86d 644 if (getaddrinfo(IP.c_str(),PStr,&Hints,&PasvAddr) != 0)
b2e465d6 645 return true;
30b30ec1
AL
646
647 return true;
648}
649 /*}}}*/
650// FTPConn::Size - Return the size of a file /*{{{*/
651// ---------------------------------------------------------------------
652/* Grab the file size from the server, 0 means no size or empty file */
650faab0 653bool FTPConn::Size(const char *Path,unsigned long long &Size)
30b30ec1
AL
654{
655 // Query the size
656 unsigned int Tag;
657 string Msg;
ce0ae89a 658 Size = 0;
30b30ec1
AL
659 if (WriteMsg(Tag,Msg,"SIZE %s",Path) == false)
660 return false;
661
662 char *End;
650faab0 663 Size = strtoull(Msg.c_str(),&End,10);
30b30ec1 664 if (Tag >= 400 || End == Msg.c_str())
ce0ae89a
AL
665 Size = 0;
666 return true;
30b30ec1
AL
667}
668 /*}}}*/
669// FTPConn::ModTime - Return the modification time of the file /*{{{*/
670// ---------------------------------------------------------------------
671/* Like Size no error is returned if the command is not supported. If the
672 command fails then time is set to the current time of day to fool
673 date checks. */
674bool FTPConn::ModTime(const char *Path, time_t &Time)
675{
676 Time = time(&Time);
677
678 // Query the mod time
679 unsigned int Tag;
680 string Msg;
681 if (WriteMsg(Tag,Msg,"MDTM %s",Path) == false)
682 return false;
683 if (Tag >= 400 || Msg.empty() == true || isdigit(Msg[0]) == 0)
684 return true;
685
686 // Parse it
96cc64a5 687 return FTPMDTMStrToTime(Msg.c_str(), Time);
30b30ec1
AL
688}
689 /*}}}*/
690// FTPConn::CreateDataFd - Get a data connection /*{{{*/
691// ---------------------------------------------------------------------
692/* Create the data connection. Call FinalizeDataFd after this though.. */
693bool FTPConn::CreateDataFd()
694{
695 close(DataFd);
696 DataFd = -1;
697
698 // Attempt to enter passive mode.
699 if (TryPassive == true)
700 {
701 if (GoPasv() == false)
702 return false;
703
704 // Oops, didn't work out, don't bother trying again.
b2e465d6 705 if (PasvAddr == 0)
30b30ec1
AL
706 TryPassive = false;
707 }
708
709 // Passive mode?
b2e465d6 710 if (PasvAddr != 0)
30b30ec1
AL
711 {
712 // Get a socket
b2e465d6
AL
713 if ((DataFd = socket(PasvAddr->ai_family,PasvAddr->ai_socktype,
714 PasvAddr->ai_protocol)) < 0)
dc738e7a 715 return _error->Errno("socket",_("Could not create a socket"));
30b30ec1
AL
716
717 // Connect to the server
718 SetNonBlock(DataFd,true);
b2e465d6 719 if (connect(DataFd,PasvAddr->ai_addr,PasvAddr->ai_addrlen) < 0 &&
30b30ec1 720 errno != EINPROGRESS)
dc738e7a 721 return _error->Errno("socket",_("Could not create a socket"));
30b30ec1
AL
722
723 /* This implements a timeout for connect by opening the connection
6d13bbca 724 nonblocking */
9b148dad 725 if (WaitFd(DataFd,true,TimeOut) == false)
dc738e7a 726 return _error->Error(_("Could not connect data socket, connection timed out"));
30b30ec1
AL
727 unsigned int Err;
728 unsigned int Len = sizeof(Err);
9b148dad 729 if (getsockopt(DataFd,SOL_SOCKET,SO_ERROR,&Err,&Len) != 0)
dc738e7a 730 return _error->Errno("getsockopt",_("Failed"));
30b30ec1 731 if (Err != 0)
dc738e7a 732 return _error->Error(_("Could not connect passive socket."));
b2e465d6 733
30b30ec1
AL
734 return true;
735 }
736
737 // Port mode :<
2de438e7
AL
738 close(DataListenFd);
739 DataListenFd = -1;
740
b2e465d6 741 // Get the information for a listening socket.
ce3c2407 742 struct addrinfo *BindAddr = NULL;
b2e465d6
AL
743 struct addrinfo Hints;
744 memset(&Hints,0,sizeof(Hints));
745 Hints.ai_socktype = SOCK_STREAM;
746 Hints.ai_flags |= AI_PASSIVE;
747 Hints.ai_family = ((struct sockaddr *)&ServerAddr)->sa_family;
cddbc86d 748 if (getaddrinfo(0,"0",&Hints,&BindAddr) != 0 || BindAddr == NULL)
dc738e7a 749 return _error->Error(_("getaddrinfo was unable to get a listening socket"));
b2e465d6
AL
750
751 // Construct the socket
752 if ((DataListenFd = socket(BindAddr->ai_family,BindAddr->ai_socktype,
753 BindAddr->ai_protocol)) < 0)
754 {
755 freeaddrinfo(BindAddr);
dc738e7a 756 return _error->Errno("socket",_("Could not create a socket"));
b2e465d6 757 }
30b30ec1 758
2de438e7 759 // Bind and listen
f8043f21 760 if (::bind(DataListenFd,BindAddr->ai_addr,BindAddr->ai_addrlen) < 0)
b2e465d6
AL
761 {
762 freeaddrinfo(BindAddr);
dc738e7a 763 return _error->Errno("bind",_("Could not bind a socket"));
b2e465d6
AL
764 }
765 freeaddrinfo(BindAddr);
2de438e7 766 if (listen(DataListenFd,1) < 0)
dc738e7a 767 return _error->Errno("listen",_("Could not listen on the socket"));
2de438e7
AL
768 SetNonBlock(DataListenFd,true);
769
770 // Determine the name to send to the remote
b2e465d6
AL
771 struct sockaddr_storage Addr;
772 socklen_t AddrLen = sizeof(Addr);
773 if (getsockname(DataListenFd,(sockaddr *)&Addr,&AddrLen) < 0)
dc738e7a
AL
774 return _error->Errno("getsockname",_("Could not determine the socket's name"));
775
30b30ec1 776
b2e465d6
AL
777 // Reverse the address. We need the server address and the data port.
778 char Name[NI_MAXHOST];
779 char Service[NI_MAXSERV];
780 char Service2[NI_MAXSERV];
781 getnameinfo((struct sockaddr *)&Addr,AddrLen,
782 Name,sizeof(Name),Service,sizeof(Service),
783 NI_NUMERICHOST|NI_NUMERICSERV);
784 getnameinfo((struct sockaddr *)&ServerAddr,ServerAddrLen,
785 Name,sizeof(Name),Service2,sizeof(Service2),
786 NI_NUMERICHOST|NI_NUMERICSERV);
787
788 // Send off an IPv4 address in the old port format
789 if (((struct sockaddr *)&Addr)->sa_family == AF_INET &&
790 ForceExtended == false)
791 {
792 // Convert the dots in the quad into commas
793 for (char *I = Name; *I != 0; I++)
794 if (*I == '.')
795 *I = ',';
796 unsigned long Port = atoi(Service);
797
798 // Send the port command
799 unsigned int Tag;
800 string Msg;
801 if (WriteMsg(Tag,Msg,"PORT %s,%d,%d",
802 Name,
803 (int)(Port >> 8) & 0xff, (int)(Port & 0xff)) == false)
804 return false;
805 if (Tag >= 400)
dc738e7a 806 return _error->Error(_("Unable to send PORT command"));
b2e465d6
AL
807 return true;
808 }
809
810 // Construct an EPRT command
811 unsigned Proto = 0;
812 for (unsigned J = 0; AFMap[J].Family != 0; J++)
813 if (AFMap[J].Family == ((struct sockaddr *)&Addr)->sa_family)
814 Proto = AFMap[J].IETFFamily;
815 if (Proto == 0)
dc738e7a 816 return _error->Error(_("Unknown address family %u (AF_*)"),
b2e465d6
AL
817 ((struct sockaddr *)&Addr)->sa_family);
818
819 // Send the EPRT command
30b30ec1
AL
820 unsigned int Tag;
821 string Msg;
b2e465d6 822 if (WriteMsg(Tag,Msg,"EPRT |%u|%s|%s|",Proto,Name,Service) == false)
30b30ec1
AL
823 return false;
824 if (Tag >= 400)
dc738e7a 825 return _error->Error(_("EPRT failed, server said: %s"),Msg.c_str());
30b30ec1
AL
826 return true;
827}
828 /*}}}*/
829// FTPConn::Finalize - Complete the Data connection /*{{{*/
830// ---------------------------------------------------------------------
831/* If the connection is in port mode this waits for the other end to hook
832 up to us. */
833bool FTPConn::Finalize()
834{
835 // Passive mode? Do nothing
b2e465d6 836 if (PasvAddr != 0)
30b30ec1
AL
837 return true;
838
839 // Close any old socket..
840 close(DataFd);
841 DataFd = -1;
842
843 // Wait for someone to connect..
844 if (WaitFd(DataListenFd,false,TimeOut) == false)
dc738e7a 845 return _error->Error(_("Data socket connect timed out"));
30b30ec1
AL
846
847 // Accept the connection
848 struct sockaddr_in Addr;
849 socklen_t Len = sizeof(Addr);
850 DataFd = accept(DataListenFd,(struct sockaddr *)&Addr,&Len);
851 if (DataFd < 0)
dc738e7a 852 return _error->Errno("accept",_("Unable to accept connection"));
30b30ec1 853
2de438e7
AL
854 close(DataListenFd);
855 DataListenFd = -1;
856
30b30ec1
AL
857 return true;
858}
859 /*}}}*/
860// FTPConn::Get - Get a file /*{{{*/
861// ---------------------------------------------------------------------
862/* This opens a data connection, sends REST and RETR and then
863 transfers the file over. */
650faab0 864bool FTPConn::Get(const char *Path,FileFd &To,unsigned long long Resume,
ee279506
MV
865 Hashes &Hash,bool &Missing, unsigned long long MaximumSize,
866 pkgAcqMethod *Owner)
30b30ec1 867{
ce0ae89a 868 Missing = false;
30b30ec1
AL
869 if (CreateDataFd() == false)
870 return false;
871
872 unsigned int Tag;
ce0ae89a 873 string Msg;
30b30ec1
AL
874 if (Resume != 0)
875 {
876 if (WriteMsg(Tag,Msg,"REST %u",Resume) == false)
877 return false;
878 if (Tag >= 400)
879 Resume = 0;
880 }
881
882 if (To.Truncate(Resume) == false)
883 return false;
10861bb5
AL
884
885 if (To.Seek(0) == false)
886 return false;
887
888 if (Resume != 0)
889 {
109eb151 890 if (Hash.AddFD(To,Resume) == false)
10861bb5 891 {
dc738e7a 892 _error->Errno("read",_("Problem hashing file"));
10861bb5
AL
893 return false;
894 }
895 }
30b30ec1
AL
896
897 // Send the get command
898 if (WriteMsg(Tag,Msg,"RETR %s",Path) == false)
899 return false;
900
901 if (Tag >= 400)
ce0ae89a
AL
902 {
903 if (Tag == 550)
904 Missing = true;
dc738e7a 905 return _error->Error(_("Unable to fetch file, server said '%s'"),Msg.c_str());
ce0ae89a 906 }
30b30ec1
AL
907
908 // Finish off the data connection
909 if (Finalize() == false)
910 return false;
911
912 // Copy loop
913 unsigned char Buffer[4096];
914 while (1)
915 {
916 // Wait for some data..
917 if (WaitFd(DataFd,false,TimeOut) == false)
25dbb396
AL
918 {
919 Close();
dc738e7a 920 return _error->Error(_("Data socket timed out"));
25dbb396
AL
921 }
922
30b30ec1
AL
923 // Read the data..
924 int Res = read(DataFd,Buffer,sizeof(Buffer));
925 if (Res == 0)
926 break;
927 if (Res < 0)
928 {
929 if (errno == EAGAIN)
930 continue;
931 break;
932 }
10861bb5 933
63b1700f 934 Hash.Add(Buffer,Res);
30b30ec1 935 if (To.Write(Buffer,Res) == false)
25dbb396
AL
936 {
937 Close();
30b30ec1 938 return false;
5b33fab8
MV
939 }
940
c48eea97 941 if (MaximumSize > 0 && To.Tell() > MaximumSize)
ee279506
MV
942 {
943 Owner->SetFailReason("MaximumSizeExceeded");
5b33fab8 944 return _error->Error("Writing more data than expected (%llu > %llu)",
c48eea97 945 To.Tell(), MaximumSize);
ee279506 946 }
30b30ec1
AL
947 }
948
949 // All done
950 close(DataFd);
951 DataFd = -1;
952
953 // Read the closing message from the server
954 if (ReadResp(Tag,Msg) == false)
955 return false;
956 if (Tag >= 400)
dc738e7a 957 return _error->Error(_("Data transfer failed, server said '%s'"),Msg.c_str());
30b30ec1
AL
958 return true;
959}
960 /*}}}*/
961
ce0ae89a
AL
962// FtpMethod::FtpMethod - Constructor /*{{{*/
963// ---------------------------------------------------------------------
964/* */
23e64f6d 965FtpMethod::FtpMethod() : aptMethod("ftp","1.0",SendConfig)
30b30ec1 966{
ce0ae89a
AL
967 signal(SIGTERM,SigTerm);
968 signal(SIGINT,SigTerm);
30b30ec1 969
ce0ae89a
AL
970 Server = 0;
971 FailFd = -1;
972}
973 /*}}}*/
974// FtpMethod::SigTerm - Handle a fatal signal /*{{{*/
975// ---------------------------------------------------------------------
1e3f4083 976/* This closes and timestamps the open file. This is necessary to get
ce0ae89a
AL
977 resume behavoir on user abort */
978void FtpMethod::SigTerm(int)
979{
980 if (FailFd == -1)
ffe9323a 981 _exit(100);
9ce3cfc9 982
ce0ae89a 983 // Timestamp
246bbb61 984 struct timeval times[2];
9ce3cfc9
DK
985 times[0].tv_sec = FailTime;
986 times[1].tv_sec = FailTime;
246bbb61
DK
987 times[0].tv_usec = times[1].tv_usec = 0;
988 utimes(FailFile.c_str(), times);
9ce3cfc9
DK
989
990 close(FailFd);
991
ffe9323a 992 _exit(100);
ce0ae89a
AL
993}
994 /*}}}*/
995// FtpMethod::Configuration - Handle a configuration message /*{{{*/
996// ---------------------------------------------------------------------
997/* We stash the desired pipeline depth */
998bool FtpMethod::Configuration(string Message)
999{
23e64f6d 1000 if (aptMethod::Configuration(Message) == false)
ce0ae89a 1001 return false;
9983999d 1002
23e64f6d 1003 TimeOut = _config->FindI("Acquire::Ftp::Timeout",TimeOut);
9983999d 1004
ce0ae89a
AL
1005 return true;
1006}
1007 /*}}}*/
1008// FtpMethod::Fetch - Fetch a file /*{{{*/
1009// ---------------------------------------------------------------------
10861bb5 1010/* Fetch a single file, called by the base class.. */
ce0ae89a
AL
1011bool FtpMethod::Fetch(FetchItem *Itm)
1012{
1013 URI Get = Itm->Uri;
1014 const char *File = Get.Path.c_str();
1015 FetchResult Res;
1016 Res.Filename = Itm->DestFile;
1017 Res.IMSHit = false;
1de1f703
MV
1018
1019 maybe_add_auth (Get, _config->FindFile("Dir::Etc::netrc"));
1020
ce0ae89a
AL
1021 // Connect to the server
1022 if (Server == 0 || Server->Comp(Get) == false)
30b30ec1 1023 {
ce0ae89a
AL
1024 delete Server;
1025 Server = new FTPConn(Get);
1026 }
1027
1028 // Could not connect is a transient error..
1029 if (Server->Open(this) == false)
1030 {
b3e53cec 1031 Server->Close();
ce0ae89a
AL
1032 Fail(true);
1033 return true;
1034 }
30b30ec1 1035
ce0ae89a 1036 // Get the files information
dc738e7a 1037 Status(_("Query"));
650faab0 1038 unsigned long long Size;
ce0ae89a
AL
1039 if (Server->Size(File,Size) == false ||
1040 Server->ModTime(File,FailTime) == false)
1041 {
1042 Fail(true);
1043 return true;
1044 }
1045 Res.Size = Size;
1046
1047 // See if it is an IMS hit
1048 if (Itm->LastModified == FailTime)
1049 {
1050 Res.Size = 0;
1051 Res.IMSHit = true;
1052 URIDone(Res);
1053 return true;
1054 }
1055
1056 // See if the file exists
1057 struct stat Buf;
1058 if (stat(Itm->DestFile.c_str(),&Buf) == 0)
1059 {
650faab0 1060 if (Size == (unsigned long long)Buf.st_size && FailTime == Buf.st_mtime)
30b30ec1 1061 {
ce0ae89a
AL
1062 Res.Size = Buf.st_size;
1063 Res.LastModified = Buf.st_mtime;
7ef72446 1064 Res.ResumePoint = Buf.st_size;
ce0ae89a
AL
1065 URIDone(Res);
1066 return true;
30b30ec1
AL
1067 }
1068
ce0ae89a 1069 // Resume?
650faab0 1070 if (FailTime == Buf.st_mtime && Size > (unsigned long long)Buf.st_size)
ce0ae89a
AL
1071 Res.ResumePoint = Buf.st_size;
1072 }
1073
1074 // Open the file
9224ce3d 1075 Hashes Hash(Itm->ExpectedHashes);
ce0ae89a
AL
1076 {
1077 FileFd Fd(Itm->DestFile,FileFd::WriteAny);
1078 if (_error->PendingError() == true)
1079 return false;
1080
1081 URIStart(Res);
1082
1083 FailFile = Itm->DestFile;
3a8776a3 1084 FailFile.c_str(); // Make sure we don't do a malloc in the signal handler
ce0ae89a
AL
1085 FailFd = Fd.Fd();
1086
1087 bool Missing;
ee279506 1088 if (Server->Get(File,Fd,Res.ResumePoint,Hash,Missing,Itm->MaximumSize,this) == false)
30b30ec1 1089 {
0dfc0829 1090 Fd.Close();
9ce3cfc9 1091
0dfc0829 1092 // Timestamp
246bbb61 1093 struct timeval times[2];
9ce3cfc9
DK
1094 times[0].tv_sec = FailTime;
1095 times[1].tv_sec = FailTime;
246bbb61
DK
1096 times[0].tv_usec = times[1].tv_usec = 0;
1097 utimes(FailFile.c_str(), times);
9ce3cfc9 1098
fc5f5417
MV
1099 // If the file is missing we hard fail and delete the destfile
1100 // otherwise transient fail
1101 if (Missing == true) {
ce1f3a2c 1102 RemoveFile("ftp", FailFile);
ce0ae89a 1103 return false;
fc5f5417 1104 }
ce0ae89a
AL
1105 Fail(true);
1106 return true;
30b30ec1 1107 }
ce0ae89a
AL
1108
1109 Res.Size = Fd.Size();
9ce3cfc9
DK
1110
1111 // Timestamp
246bbb61 1112 struct timeval times[2];
9ce3cfc9
DK
1113 times[0].tv_sec = FailTime;
1114 times[1].tv_sec = FailTime;
246bbb61
DK
1115 times[0].tv_usec = times[1].tv_usec = 0;
1116 utimes(Fd.Name().c_str(), times);
9ce3cfc9 1117 FailFd = -1;
30b30ec1 1118 }
9ce3cfc9 1119
ce0ae89a 1120 Res.LastModified = FailTime;
a7c835af 1121 Res.TakeHashes(Hash);
ce0ae89a
AL
1122
1123 URIDone(Res);
9ce3cfc9 1124
ce0ae89a
AL
1125 return true;
1126}
1127 /*}}}*/
1128
65512241 1129int main(int, const char *argv[])
8b79c94a 1130{
d4489322
AL
1131 /* See if we should be come the http client - we do this for http
1132 proxy urls */
1133 if (getenv("ftp_proxy") != 0)
1134 {
1135 URI Proxy = string(getenv("ftp_proxy"));
f8081133 1136
f8081133 1137 // Run the HTTP method
d4489322
AL
1138 if (Proxy.Access == "http")
1139 {
1140 // Copy over the environment setting
1141 char S[300];
1142 snprintf(S,sizeof(S),"http_proxy=%s",getenv("ftp_proxy"));
1143 putenv(S);
b8564336 1144 putenv((char *)"no_proxy=");
d4489322
AL
1145
1146 // Run the http method
91bb3e2e 1147 string Path = flNotFile(argv[0]) + "http";
42ab8223 1148 execl(Path.c_str(),Path.c_str(),(char *)NULL);
dc738e7a 1149 cerr << _("Unable to invoke ") << Path << endl;
d4489322
AL
1150 exit(100);
1151 }
1152 }
8b79c94a 1153 return FtpMethod().Run();
30b30ec1 1154}