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