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