]> git.saurik.com Git - apt.git/blob - methods/ftp.cc
Merge remote-tracking branch 'mvo/feature/install-progress' into debian/sid
[apt.git] / methods / ftp.cc
1 // -*- mode: cpp; mode: fold -*-
2 // Description /*{{{*/
3 // $Id: ftp.cc,v 1.31.2.1 2004/01/16 18:58:50 mdz Exp $
4 /* ######################################################################
5
6 FTP Aquire Method - This is the FTP aquire method for APT.
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.
12
13 RFC 2428 describes the IPv6 FTP behavior
14
15 ##################################################################### */
16 /*}}}*/
17 // Include Files /*{{{*/
18 #include <config.h>
19
20 #include <apt-pkg/fileutl.h>
21 #include <apt-pkg/acquire-method.h>
22 #include <apt-pkg/error.h>
23 #include <apt-pkg/hashes.h>
24 #include <apt-pkg/netrc.h>
25 #include <apt-pkg/configuration.h>
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>
35 #include <iostream>
36
37 // Internet stuff
38 #include <netinet/in.h>
39 #include <sys/socket.h>
40 #include <arpa/inet.h>
41 #include <netdb.h>
42
43 #include "rfc2553emu.h"
44 #include "connect.h"
45 #include "ftp.h"
46 #include <apti18n.h>
47 /*}}}*/
48
49 using namespace std;
50
51 /* This table is for the EPRT and EPSV commands, it maps the OS address
52 family to the IETF address families */
53 struct AFMap
54 {
55 unsigned long Family;
56 unsigned long IETFFamily;
57 };
58
59 #ifndef AF_INET6
60 struct AFMap AFMap[] = {{AF_INET,1},{}};
61 #else
62 struct AFMap AFMap[] = {{AF_INET,1},{AF_INET6,2},{}};
63 #endif
64
65 unsigned long TimeOut = 120;
66 URI Proxy;
67 string FtpMethod::FailFile;
68 int FtpMethod::FailFd = -1;
69 time_t FtpMethod::FailTime = 0;
70
71 // FTPConn::FTPConn - Constructor /*{{{*/
72 // ---------------------------------------------------------------------
73 /* */
74 FTPConn::FTPConn(URI Srv) : Len(0), ServerFd(-1), DataFd(-1),
75 DataListenFd(-1), ServerName(Srv),
76 ForceExtended(false), TryPassive(true)
77 {
78 Debug = _config->FindB("Debug::Acquire::Ftp",false);
79 PasvAddr = 0;
80 Buffer[0] = '\0';
81 }
82 /*}}}*/
83 // FTPConn::~FTPConn - Destructor /*{{{*/
84 // ---------------------------------------------------------------------
85 /* */
86 FTPConn::~FTPConn()
87 {
88 Close();
89 }
90 /*}}}*/
91 // FTPConn::Close - Close down the connection /*{{{*/
92 // ---------------------------------------------------------------------
93 /* Just tear down the socket and data socket */
94 void FTPConn::Close()
95 {
96 close(ServerFd);
97 ServerFd = -1;
98 close(DataFd);
99 DataFd = -1;
100 close(DataListenFd);
101 DataListenFd = -1;
102
103 if (PasvAddr != 0)
104 freeaddrinfo(PasvAddr);
105 PasvAddr = 0;
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. */
112 bool FTPConn::Open(pkgAcqMethod *Owner)
113 {
114 // Use the already open connection if possible.
115 if (ServerFd != -1)
116 return true;
117
118 Close();
119
120 // Determine the proxy setting
121 string SpecificProxy = _config->Find("Acquire::ftp::Proxy::" + ServerName.Host);
122 if (!SpecificProxy.empty())
123 {
124 if (SpecificProxy == "DIRECT")
125 Proxy = "";
126 else
127 Proxy = SpecificProxy;
128 }
129 else
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
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
150 // Determine what host and port to use based on the proxy settings
151 int Port = 0;
152 string Host;
153 if (Proxy.empty() == true)
154 {
155 if (ServerName.Port != 0)
156 Port = ServerName.Port;
157 Host = ServerName.Host;
158 }
159 else
160 {
161 if (Proxy.Port != 0)
162 Port = Proxy.Port;
163 Host = Proxy.Host;
164 }
165
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();
169 if (Connect(Host,Port,"ftp",21,ServerFd,TimeOut,Owner) == false)
170 return false;
171
172 // Login must be before getpeername otherwise dante won't work.
173 Owner->Status(_("Logging in"));
174 bool Res = Login();
175
176 // Get the remote server's address
177 PeerAddrLen = sizeof(PeerAddr);
178 if (getpeername(ServerFd,(sockaddr *)&PeerAddr,&PeerAddrLen) != 0)
179 return _error->Errno("getpeername",_("Unable to determine the peer name"));
180
181 // Get the local machine's address
182 ServerAddrLen = sizeof(ServerAddr);
183 if (getsockname(ServerFd,(sockaddr *)&ServerAddr,&ServerAddrLen) != 0)
184 return _error->Errno("getsockname",_("Unable to determine the local name"));
185
186 return Res;
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. */
193 bool FTPConn::Login()
194 {
195 unsigned int Tag;
196 string Msg;
197
198 // Setup the variables needed for authentication
199 string User = "anonymous";
200 string Pass = "apt_get_ftp_2.1@debian.linux.user";
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)
215 return _error->Error(_("The server refused the connection and said: %s"),Msg.c_str());
216
217 // Send the user
218 if (WriteMsg(Tag,Msg,"USER %s",User.c_str()) == false)
219 return false;
220 if (Tag >= 400)
221 return _error->Error(_("USER failed, server said: %s"),Msg.c_str());
222
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 }
230
231 // Enter passive mode
232 if (_config->Exists("Acquire::FTP::Passive::" + ServerName.Host) == true)
233 TryPassive = _config->FindB("Acquire::FTP::Passive::" + ServerName.Host,true);
234 else
235 TryPassive = _config->FindB("Acquire::FTP::Passive",true);
236 }
237 else
238 {
239 // Read the initial response
240 if (ReadResp(Tag,Msg) == false)
241 return false;
242 if (Tag >= 400)
243 return _error->Error(_("The server refused the connection and said: %s"),Msg.c_str());
244
245 // Perform proxy script execution
246 Configuration::Item const *Opts = _config->Tree("Acquire::ftp::ProxyLogin");
247 if (Opts == 0 || Opts->Child == 0)
248 return _error->Error(_("A proxy server was specified but no login "
249 "script, Acquire::ftp::ProxyLogin is empty."));
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];
260 if (ServerName.Port != 0)
261 sprintf(SitePort,"%u",ServerName.Port);
262 else
263 strcpy(SitePort,"21");
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)
276 return _error->Error(_("Login script command '%s' failed, server said: %s"),Tmp.c_str(),Msg.c_str());
277 }
278
279 // Enter passive mode
280 TryPassive = false;
281 if (_config->Exists("Acquire::FTP::Passive::" + ServerName.Host) == true)
282 TryPassive = _config->FindB("Acquire::FTP::Passive::" + ServerName.Host,true);
283 else
284 {
285 if (_config->Exists("Acquire::FTP::Proxy::Passive") == true)
286 TryPassive = _config->FindB("Acquire::FTP::Proxy::Passive",true);
287 else
288 TryPassive = _config->FindB("Acquire::FTP::Passive",true);
289 }
290 }
291
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
298 // Binary mode
299 if (WriteMsg(Tag,Msg,"TYPE I") == false)
300 return false;
301 if (Tag >= 400)
302 return _error->Error(_("TYPE failed, server said: %s"),Msg.c_str());
303
304 return true;
305 }
306 /*}}}*/
307 // FTPConn::ReadLine - Read a line from the server /*{{{*/
308 // ---------------------------------------------------------------------
309 /* This performs a very simple buffered read. */
310 bool FTPConn::ReadLine(string &Text)
311 {
312 if (ServerFd == -1)
313 return false;
314
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)
338 {
339 Close();
340 return _error->Error(_("Connection timeout"));
341 }
342
343 // Suck it back
344 int Res = read(ServerFd,Buffer + Len,sizeof(Buffer) - Len);
345 if (Res == 0)
346 _error->Error(_("Server closed the connection"));
347 if (Res <= 0)
348 {
349 _error->Errno("read",_("Read error"));
350 Close();
351 return false;
352 }
353 Len += Res;
354 }
355
356 return _error->Error(_("A response overflowed the buffer."));
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 */
362 bool 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)
373 return _error->Error(_("Protocol corruption"));
374
375 // All done ?
376 Text = Msg.c_str()+4;
377 if (*End == ' ')
378 {
379 if (Debug == true)
380 cerr << "<- '" << QuoteString(Text,"") << "'" << endl;
381 return true;
382 }
383
384 if (*End != '-')
385 return _error->Error(_("Protocol corruption"));
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)
422 cerr << "<- '" << QuoteString(Text,"") << "'" << endl;
423
424 return !_error->PendingError();
425 }
426 /*}}}*/
427 // FTPConn::WriteMsg - Send a message to the server /*{{{*/
428 // ---------------------------------------------------------------------
429 /* Simple printf like function.. */
430 bool 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");
439 va_end(args);
440
441 if (Debug == true)
442 cerr << "-> '" << QuoteString(S,"") << "'" << endl;
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)
450 {
451 Close();
452 return _error->Error(_("Connection timeout"));
453 }
454
455 int Res = write(ServerFd,S + Start,Len);
456 if (Res <= 0)
457 {
458 _error->Errno("write",_("Write error"));
459 Close();
460 return false;
461 }
462
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.
474 We have to enter passive mode every time we make a data connection :| */
475 bool FTPConn::GoPasv()
476 {
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
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)
496 return true;
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)
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)
506 {
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;
525 return true;
526 }
527
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;
533
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 */
547 bool 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++;
568 for (string::const_iterator I = Msg.begin() + Pos; I < Msg.end(); ++I)
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)
604 {
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;
613 }
614 else
615 {
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;
623 }
624
625 // Get a new passive address.
626 if (getaddrinfo(IP.c_str(),PStr,&Hints,&PasvAddr) != 0)
627 return true;
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 */
635 bool FTPConn::Size(const char *Path,unsigned long long &Size)
636 {
637 // Query the size
638 unsigned int Tag;
639 string Msg;
640 Size = 0;
641 if (WriteMsg(Tag,Msg,"SIZE %s",Path) == false)
642 return false;
643
644 char *End;
645 Size = strtoull(Msg.c_str(),&End,10);
646 if (Tag >= 400 || End == Msg.c_str())
647 Size = 0;
648 return true;
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. */
656 bool 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
669 return FTPMDTMStrToTime(Msg.c_str(), Time);
670 }
671 /*}}}*/
672 // FTPConn::CreateDataFd - Get a data connection /*{{{*/
673 // ---------------------------------------------------------------------
674 /* Create the data connection. Call FinalizeDataFd after this though.. */
675 bool 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.
687 if (PasvAddr == 0)
688 TryPassive = false;
689 }
690
691 // Passive mode?
692 if (PasvAddr != 0)
693 {
694 // Get a socket
695 if ((DataFd = socket(PasvAddr->ai_family,PasvAddr->ai_socktype,
696 PasvAddr->ai_protocol)) < 0)
697 return _error->Errno("socket",_("Could not create a socket"));
698
699 // Connect to the server
700 SetNonBlock(DataFd,true);
701 if (connect(DataFd,PasvAddr->ai_addr,PasvAddr->ai_addrlen) < 0 &&
702 errno != EINPROGRESS)
703 return _error->Errno("socket",_("Could not create a socket"));
704
705 /* This implements a timeout for connect by opening the connection
706 nonblocking */
707 if (WaitFd(DataFd,true,TimeOut) == false)
708 return _error->Error(_("Could not connect data socket, connection timed out"));
709 unsigned int Err;
710 unsigned int Len = sizeof(Err);
711 if (getsockopt(DataFd,SOL_SOCKET,SO_ERROR,&Err,&Len) != 0)
712 return _error->Errno("getsockopt",_("Failed"));
713 if (Err != 0)
714 return _error->Error(_("Could not connect passive socket."));
715
716 return true;
717 }
718
719 // Port mode :<
720 close(DataListenFd);
721 DataListenFd = -1;
722
723 // Get the information for a listening socket.
724 struct addrinfo *BindAddr = NULL;
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;
730 if (getaddrinfo(0,"0",&Hints,&BindAddr) != 0 || BindAddr == NULL)
731 return _error->Error(_("getaddrinfo was unable to get a listening socket"));
732
733 // Construct the socket
734 if ((DataListenFd = socket(BindAddr->ai_family,BindAddr->ai_socktype,
735 BindAddr->ai_protocol)) < 0)
736 {
737 freeaddrinfo(BindAddr);
738 return _error->Errno("socket",_("Could not create a socket"));
739 }
740
741 // Bind and listen
742 if (bind(DataListenFd,BindAddr->ai_addr,BindAddr->ai_addrlen) < 0)
743 {
744 freeaddrinfo(BindAddr);
745 return _error->Errno("bind",_("Could not bind a socket"));
746 }
747 freeaddrinfo(BindAddr);
748 if (listen(DataListenFd,1) < 0)
749 return _error->Errno("listen",_("Could not listen on the socket"));
750 SetNonBlock(DataListenFd,true);
751
752 // Determine the name to send to the remote
753 struct sockaddr_storage Addr;
754 socklen_t AddrLen = sizeof(Addr);
755 if (getsockname(DataListenFd,(sockaddr *)&Addr,&AddrLen) < 0)
756 return _error->Errno("getsockname",_("Could not determine the socket's name"));
757
758
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)
788 return _error->Error(_("Unable to send PORT command"));
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)
798 return _error->Error(_("Unknown address family %u (AF_*)"),
799 ((struct sockaddr *)&Addr)->sa_family);
800
801 // Send the EPRT command
802 unsigned int Tag;
803 string Msg;
804 if (WriteMsg(Tag,Msg,"EPRT |%u|%s|%s|",Proto,Name,Service) == false)
805 return false;
806 if (Tag >= 400)
807 return _error->Error(_("EPRT failed, server said: %s"),Msg.c_str());
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. */
815 bool FTPConn::Finalize()
816 {
817 // Passive mode? Do nothing
818 if (PasvAddr != 0)
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)
827 return _error->Error(_("Data socket connect timed out"));
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)
834 return _error->Errno("accept",_("Unable to accept connection"));
835
836 close(DataListenFd);
837 DataListenFd = -1;
838
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. */
846 bool FTPConn::Get(const char *Path,FileFd &To,unsigned long long Resume,
847 Hashes &Hash,bool &Missing)
848 {
849 Missing = false;
850 if (CreateDataFd() == false)
851 return false;
852
853 unsigned int Tag;
854 string Msg;
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;
865
866 if (To.Seek(0) == false)
867 return false;
868
869 if (Resume != 0)
870 {
871 if (Hash.AddFD(To,Resume) == false)
872 {
873 _error->Errno("read",_("Problem hashing file"));
874 return false;
875 }
876 }
877
878 // Send the get command
879 if (WriteMsg(Tag,Msg,"RETR %s",Path) == false)
880 return false;
881
882 if (Tag >= 400)
883 {
884 if (Tag == 550)
885 Missing = true;
886 return _error->Error(_("Unable to fetch file, server said '%s'"),Msg.c_str());
887 }
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)
899 {
900 Close();
901 return _error->Error(_("Data socket timed out"));
902 }
903
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 }
914
915 Hash.Add(Buffer,Res);
916 if (To.Write(Buffer,Res) == false)
917 {
918 Close();
919 return false;
920 }
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)
931 return _error->Error(_("Data transfer failed, server said '%s'"),Msg.c_str());
932 return true;
933 }
934 /*}}}*/
935
936 // FtpMethod::FtpMethod - Constructor /*{{{*/
937 // ---------------------------------------------------------------------
938 /* */
939 FtpMethod::FtpMethod() : pkgAcqMethod("1.0",SendConfig)
940 {
941 signal(SIGTERM,SigTerm);
942 signal(SIGINT,SigTerm);
943
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 */
952 void FtpMethod::SigTerm(int)
953 {
954 if (FailFd == -1)
955 _exit(100);
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
964 _exit(100);
965 }
966 /*}}}*/
967 // FtpMethod::Configuration - Handle a configuration message /*{{{*/
968 // ---------------------------------------------------------------------
969 /* We stash the desired pipeline depth */
970 bool 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 // ---------------------------------------------------------------------
981 /* Fetch a single file, called by the base class.. */
982 bool 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;
989
990 maybe_add_auth (Get, _config->FindFile("Dir::Etc::netrc"));
991
992 // Connect to the server
993 if (Server == 0 || Server->Comp(Get) == false)
994 {
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 {
1002 Server->Close();
1003 Fail(true);
1004 return true;
1005 }
1006
1007 // Get the files information
1008 Status(_("Query"));
1009 unsigned long long Size;
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 {
1031 if (Size == (unsigned long long)Buf.st_size && FailTime == Buf.st_mtime)
1032 {
1033 Res.Size = Buf.st_size;
1034 Res.LastModified = Buf.st_mtime;
1035 Res.ResumePoint = Buf.st_size;
1036 URIDone(Res);
1037 return true;
1038 }
1039
1040 // Resume?
1041 if (FailTime == Buf.st_mtime && Size > (unsigned long long)Buf.st_size)
1042 Res.ResumePoint = Buf.st_size;
1043 }
1044
1045 // Open the file
1046 Hashes Hash;
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;
1059 if (Server->Get(File,Fd,Res.ResumePoint,Hash,Missing) == false)
1060 {
1061 Fd.Close();
1062
1063 // Timestamp
1064 struct utimbuf UBuf;
1065 UBuf.actime = FailTime;
1066 UBuf.modtime = FailTime;
1067 utime(FailFile.c_str(),&UBuf);
1068
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());
1073 return false;
1074 }
1075 Fail(true);
1076 return true;
1077 }
1078
1079 Res.Size = Fd.Size();
1080 }
1081
1082 Res.LastModified = FailTime;
1083 Res.TakeHashes(Hash);
1084
1085 // Timestamp
1086 struct utimbuf UBuf;
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
1098 int main(int argc,const char *argv[])
1099 {
1100 setlocale(LC_ALL, "");
1101
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"));
1107
1108 // Run the HTTP method
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);
1115 putenv((char *)"no_proxy=");
1116
1117 // Run the http method
1118 string Path = flNotFile(argv[0]) + "http";
1119 execl(Path.c_str(),Path.c_str(),(char *)NULL);
1120 cerr << _("Unable to invoke ") << Path << endl;
1121 exit(100);
1122 }
1123 }
1124
1125 FtpMethod Mth;
1126
1127 return Mth.Run();
1128 }