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