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