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