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