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