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