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