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