]> git.saurik.com Git - apt.git/blame_incremental - methods/ftp.cc
fix -Wmissing-field-initializers warnings
[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 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
27#include <sys/stat.h>
28#include <sys/time.h>
29#include <unistd.h>
30#include <signal.h>
31#include <stdio.h>
32#include <errno.h>
33#include <stdarg.h>
34#include <iostream>
35
36// Internet stuff
37#include <netinet/in.h>
38#include <sys/socket.h>
39#include <arpa/inet.h>
40#include <netdb.h>
41
42#include "rfc2553emu.h"
43#include "connect.h"
44#include "ftp.h"
45#include <apti18n.h>
46 /*}}}*/
47
48using namespace std;
49
50/* This table is for the EPRT and EPSV commands, it maps the OS address
51 family to the IETF address families */
52struct AFMap
53{
54 unsigned long Family;
55 unsigned long IETFFamily;
56};
57
58#ifndef AF_INET6
59struct AFMap AFMap[] = {{AF_INET,1},{0, 0}};
60#else
61struct AFMap AFMap[] = {{AF_INET,1},{AF_INET6,2},{0, 0}};
62#endif
63
64unsigned long TimeOut = 120;
65URI Proxy;
66string FtpMethod::FailFile;
67int FtpMethod::FailFd = -1;
68time_t FtpMethod::FailTime = 0;
69
70// FTPConn::FTPConn - Constructor /*{{{*/
71// ---------------------------------------------------------------------
72/* */
73FTPConn::FTPConn(URI Srv) : Len(0), ServerFd(-1), DataFd(-1),
74 DataListenFd(-1), ServerName(Srv),
75 ForceExtended(false), TryPassive(true)
76{
77 Debug = _config->FindB("Debug::Acquire::Ftp",false);
78 PasvAddr = 0;
79 Buffer[0] = '\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 va_end(args);
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 if (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 = NULL;
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 if (getaddrinfo(0,"0",&Hints,&BindAddr) != 0 || BindAddr == NULL)
730 return _error->Error(_("getaddrinfo was unable to get a listening socket"));
731
732 // Construct the socket
733 if ((DataListenFd = socket(BindAddr->ai_family,BindAddr->ai_socktype,
734 BindAddr->ai_protocol)) < 0)
735 {
736 freeaddrinfo(BindAddr);
737 return _error->Errno("socket",_("Could not create a socket"));
738 }
739
740 // Bind and listen
741 if (bind(DataListenFd,BindAddr->ai_addr,BindAddr->ai_addrlen) < 0)
742 {
743 freeaddrinfo(BindAddr);
744 return _error->Errno("bind",_("Could not bind a socket"));
745 }
746 freeaddrinfo(BindAddr);
747 if (listen(DataListenFd,1) < 0)
748 return _error->Errno("listen",_("Could not listen on the socket"));
749 SetNonBlock(DataListenFd,true);
750
751 // Determine the name to send to the remote
752 struct sockaddr_storage Addr;
753 socklen_t AddrLen = sizeof(Addr);
754 if (getsockname(DataListenFd,(sockaddr *)&Addr,&AddrLen) < 0)
755 return _error->Errno("getsockname",_("Could not determine the socket's name"));
756
757
758 // Reverse the address. We need the server address and the data port.
759 char Name[NI_MAXHOST];
760 char Service[NI_MAXSERV];
761 char Service2[NI_MAXSERV];
762 getnameinfo((struct sockaddr *)&Addr,AddrLen,
763 Name,sizeof(Name),Service,sizeof(Service),
764 NI_NUMERICHOST|NI_NUMERICSERV);
765 getnameinfo((struct sockaddr *)&ServerAddr,ServerAddrLen,
766 Name,sizeof(Name),Service2,sizeof(Service2),
767 NI_NUMERICHOST|NI_NUMERICSERV);
768
769 // Send off an IPv4 address in the old port format
770 if (((struct sockaddr *)&Addr)->sa_family == AF_INET &&
771 ForceExtended == false)
772 {
773 // Convert the dots in the quad into commas
774 for (char *I = Name; *I != 0; I++)
775 if (*I == '.')
776 *I = ',';
777 unsigned long Port = atoi(Service);
778
779 // Send the port command
780 unsigned int Tag;
781 string Msg;
782 if (WriteMsg(Tag,Msg,"PORT %s,%d,%d",
783 Name,
784 (int)(Port >> 8) & 0xff, (int)(Port & 0xff)) == false)
785 return false;
786 if (Tag >= 400)
787 return _error->Error(_("Unable to send PORT command"));
788 return true;
789 }
790
791 // Construct an EPRT command
792 unsigned Proto = 0;
793 for (unsigned J = 0; AFMap[J].Family != 0; J++)
794 if (AFMap[J].Family == ((struct sockaddr *)&Addr)->sa_family)
795 Proto = AFMap[J].IETFFamily;
796 if (Proto == 0)
797 return _error->Error(_("Unknown address family %u (AF_*)"),
798 ((struct sockaddr *)&Addr)->sa_family);
799
800 // Send the EPRT command
801 unsigned int Tag;
802 string Msg;
803 if (WriteMsg(Tag,Msg,"EPRT |%u|%s|%s|",Proto,Name,Service) == false)
804 return false;
805 if (Tag >= 400)
806 return _error->Error(_("EPRT failed, server said: %s"),Msg.c_str());
807 return true;
808}
809 /*}}}*/
810// FTPConn::Finalize - Complete the Data connection /*{{{*/
811// ---------------------------------------------------------------------
812/* If the connection is in port mode this waits for the other end to hook
813 up to us. */
814bool FTPConn::Finalize()
815{
816 // Passive mode? Do nothing
817 if (PasvAddr != 0)
818 return true;
819
820 // Close any old socket..
821 close(DataFd);
822 DataFd = -1;
823
824 // Wait for someone to connect..
825 if (WaitFd(DataListenFd,false,TimeOut) == false)
826 return _error->Error(_("Data socket connect timed out"));
827
828 // Accept the connection
829 struct sockaddr_in Addr;
830 socklen_t Len = sizeof(Addr);
831 DataFd = accept(DataListenFd,(struct sockaddr *)&Addr,&Len);
832 if (DataFd < 0)
833 return _error->Errno("accept",_("Unable to accept connection"));
834
835 close(DataListenFd);
836 DataListenFd = -1;
837
838 return true;
839}
840 /*}}}*/
841// FTPConn::Get - Get a file /*{{{*/
842// ---------------------------------------------------------------------
843/* This opens a data connection, sends REST and RETR and then
844 transfers the file over. */
845bool FTPConn::Get(const char *Path,FileFd &To,unsigned long long Resume,
846 Hashes &Hash,bool &Missing)
847{
848 Missing = false;
849 if (CreateDataFd() == false)
850 return false;
851
852 unsigned int Tag;
853 string Msg;
854 if (Resume != 0)
855 {
856 if (WriteMsg(Tag,Msg,"REST %u",Resume) == false)
857 return false;
858 if (Tag >= 400)
859 Resume = 0;
860 }
861
862 if (To.Truncate(Resume) == false)
863 return false;
864
865 if (To.Seek(0) == false)
866 return false;
867
868 if (Resume != 0)
869 {
870 if (Hash.AddFD(To,Resume) == false)
871 {
872 _error->Errno("read",_("Problem hashing file"));
873 return false;
874 }
875 }
876
877 // Send the get command
878 if (WriteMsg(Tag,Msg,"RETR %s",Path) == false)
879 return false;
880
881 if (Tag >= 400)
882 {
883 if (Tag == 550)
884 Missing = true;
885 return _error->Error(_("Unable to fetch file, server said '%s'"),Msg.c_str());
886 }
887
888 // Finish off the data connection
889 if (Finalize() == false)
890 return false;
891
892 // Copy loop
893 unsigned char Buffer[4096];
894 while (1)
895 {
896 // Wait for some data..
897 if (WaitFd(DataFd,false,TimeOut) == false)
898 {
899 Close();
900 return _error->Error(_("Data socket timed out"));
901 }
902
903 // Read the data..
904 int Res = read(DataFd,Buffer,sizeof(Buffer));
905 if (Res == 0)
906 break;
907 if (Res < 0)
908 {
909 if (errno == EAGAIN)
910 continue;
911 break;
912 }
913
914 Hash.Add(Buffer,Res);
915 if (To.Write(Buffer,Res) == false)
916 {
917 Close();
918 return false;
919 }
920 }
921
922 // All done
923 close(DataFd);
924 DataFd = -1;
925
926 // Read the closing message from the server
927 if (ReadResp(Tag,Msg) == false)
928 return false;
929 if (Tag >= 400)
930 return _error->Error(_("Data transfer failed, server said '%s'"),Msg.c_str());
931 return true;
932}
933 /*}}}*/
934
935// FtpMethod::FtpMethod - Constructor /*{{{*/
936// ---------------------------------------------------------------------
937/* */
938FtpMethod::FtpMethod() : pkgAcqMethod("1.0",SendConfig)
939{
940 signal(SIGTERM,SigTerm);
941 signal(SIGINT,SigTerm);
942
943 Server = 0;
944 FailFd = -1;
945}
946 /*}}}*/
947// FtpMethod::SigTerm - Handle a fatal signal /*{{{*/
948// ---------------------------------------------------------------------
949/* This closes and timestamps the open file. This is necessary to get
950 resume behavoir on user abort */
951void FtpMethod::SigTerm(int)
952{
953 if (FailFd == -1)
954 _exit(100);
955
956 // Timestamp
957 struct timeval times[2];
958 times[0].tv_sec = FailTime;
959 times[1].tv_sec = FailTime;
960 times[0].tv_usec = times[1].tv_usec = 0;
961 utimes(FailFile.c_str(), times);
962
963 close(FailFd);
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 timeval times[2];
1066 times[0].tv_sec = FailTime;
1067 times[1].tv_sec = FailTime;
1068 times[0].tv_usec = times[1].tv_usec = 0;
1069 utimes(FailFile.c_str(), times);
1070
1071 // If the file is missing we hard fail and delete the destfile
1072 // otherwise transient fail
1073 if (Missing == true) {
1074 unlink(FailFile.c_str());
1075 return false;
1076 }
1077 Fail(true);
1078 return true;
1079 }
1080
1081 Res.Size = Fd.Size();
1082
1083 // Timestamp
1084 struct timeval times[2];
1085 times[0].tv_sec = FailTime;
1086 times[1].tv_sec = FailTime;
1087 times[0].tv_usec = times[1].tv_usec = 0;
1088 utimes(Fd.Name().c_str(), times);
1089 FailFd = -1;
1090 }
1091
1092 Res.LastModified = FailTime;
1093 Res.TakeHashes(Hash);
1094
1095 URIDone(Res);
1096
1097 return true;
1098}
1099 /*}}}*/
1100
1101int main(int argc,const char *argv[])
1102{
1103 setlocale(LC_ALL, "");
1104
1105 /* See if we should be come the http client - we do this for http
1106 proxy urls */
1107 if (getenv("ftp_proxy") != 0)
1108 {
1109 URI Proxy = string(getenv("ftp_proxy"));
1110
1111 // Run the HTTP method
1112 if (Proxy.Access == "http")
1113 {
1114 // Copy over the environment setting
1115 char S[300];
1116 snprintf(S,sizeof(S),"http_proxy=%s",getenv("ftp_proxy"));
1117 putenv(S);
1118 putenv((char *)"no_proxy=");
1119
1120 // Run the http method
1121 string Path = flNotFile(argv[0]) + "http";
1122 execl(Path.c_str(),Path.c_str(),(char *)NULL);
1123 cerr << _("Unable to invoke ") << Path << endl;
1124 exit(100);
1125 }
1126 }
1127
1128 FtpMethod Mth;
1129
1130 return Mth.Run();
1131}