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