]> git.saurik.com Git - apt.git/blob - methods/ftp.cc
* merge lp:~mvo/apt/netrc branch, this adds support for a
[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 StrToTime(Msg,Time);
665 return true;
666 }
667 /*}}}*/
668 // FTPConn::CreateDataFd - Get a data connection /*{{{*/
669 // ---------------------------------------------------------------------
670 /* Create the data connection. Call FinalizeDataFd after this though.. */
671 bool FTPConn::CreateDataFd()
672 {
673 close(DataFd);
674 DataFd = -1;
675
676 // Attempt to enter passive mode.
677 if (TryPassive == true)
678 {
679 if (GoPasv() == false)
680 return false;
681
682 // Oops, didn't work out, don't bother trying again.
683 if (PasvAddr == 0)
684 TryPassive = false;
685 }
686
687 // Passive mode?
688 if (PasvAddr != 0)
689 {
690 // Get a socket
691 if ((DataFd = socket(PasvAddr->ai_family,PasvAddr->ai_socktype,
692 PasvAddr->ai_protocol)) < 0)
693 return _error->Errno("socket",_("Could not create a socket"));
694
695 // Connect to the server
696 SetNonBlock(DataFd,true);
697 if (connect(DataFd,PasvAddr->ai_addr,PasvAddr->ai_addrlen) < 0 &&
698 errno != EINPROGRESS)
699 return _error->Errno("socket",_("Could not create a socket"));
700
701 /* This implements a timeout for connect by opening the connection
702 nonblocking */
703 if (WaitFd(DataFd,true,TimeOut) == false)
704 return _error->Error(_("Could not connect data socket, connection timed out"));
705 unsigned int Err;
706 unsigned int Len = sizeof(Err);
707 if (getsockopt(DataFd,SOL_SOCKET,SO_ERROR,&Err,&Len) != 0)
708 return _error->Errno("getsockopt",_("Failed"));
709 if (Err != 0)
710 return _error->Error(_("Could not connect passive socket."));
711
712 return true;
713 }
714
715 // Port mode :<
716 close(DataListenFd);
717 DataListenFd = -1;
718
719 // Get the information for a listening socket.
720 struct addrinfo *BindAddr = 0;
721 struct addrinfo Hints;
722 memset(&Hints,0,sizeof(Hints));
723 Hints.ai_socktype = SOCK_STREAM;
724 Hints.ai_flags |= AI_PASSIVE;
725 Hints.ai_family = ((struct sockaddr *)&ServerAddr)->sa_family;
726 int Res;
727 if ((Res = getaddrinfo(0,"0",&Hints,&BindAddr)) != 0)
728 return _error->Error(_("getaddrinfo was unable to get a listening socket"));
729
730 // Construct the socket
731 if ((DataListenFd = socket(BindAddr->ai_family,BindAddr->ai_socktype,
732 BindAddr->ai_protocol)) < 0)
733 {
734 freeaddrinfo(BindAddr);
735 return _error->Errno("socket",_("Could not create a socket"));
736 }
737
738 // Bind and listen
739 if (bind(DataListenFd,BindAddr->ai_addr,BindAddr->ai_addrlen) < 0)
740 {
741 freeaddrinfo(BindAddr);
742 return _error->Errno("bind",_("Could not bind a socket"));
743 }
744 freeaddrinfo(BindAddr);
745 if (listen(DataListenFd,1) < 0)
746 return _error->Errno("listen",_("Could not listen on the socket"));
747 SetNonBlock(DataListenFd,true);
748
749 // Determine the name to send to the remote
750 struct sockaddr_storage Addr;
751 socklen_t AddrLen = sizeof(Addr);
752 if (getsockname(DataListenFd,(sockaddr *)&Addr,&AddrLen) < 0)
753 return _error->Errno("getsockname",_("Could not determine the socket's name"));
754
755
756 // Reverse the address. We need the server address and the data port.
757 char Name[NI_MAXHOST];
758 char Service[NI_MAXSERV];
759 char Service2[NI_MAXSERV];
760 getnameinfo((struct sockaddr *)&Addr,AddrLen,
761 Name,sizeof(Name),Service,sizeof(Service),
762 NI_NUMERICHOST|NI_NUMERICSERV);
763 getnameinfo((struct sockaddr *)&ServerAddr,ServerAddrLen,
764 Name,sizeof(Name),Service2,sizeof(Service2),
765 NI_NUMERICHOST|NI_NUMERICSERV);
766
767 // Send off an IPv4 address in the old port format
768 if (((struct sockaddr *)&Addr)->sa_family == AF_INET &&
769 ForceExtended == false)
770 {
771 // Convert the dots in the quad into commas
772 for (char *I = Name; *I != 0; I++)
773 if (*I == '.')
774 *I = ',';
775 unsigned long Port = atoi(Service);
776
777 // Send the port command
778 unsigned int Tag;
779 string Msg;
780 if (WriteMsg(Tag,Msg,"PORT %s,%d,%d",
781 Name,
782 (int)(Port >> 8) & 0xff, (int)(Port & 0xff)) == false)
783 return false;
784 if (Tag >= 400)
785 return _error->Error(_("Unable to send PORT command"));
786 return true;
787 }
788
789 // Construct an EPRT command
790 unsigned Proto = 0;
791 for (unsigned J = 0; AFMap[J].Family != 0; J++)
792 if (AFMap[J].Family == ((struct sockaddr *)&Addr)->sa_family)
793 Proto = AFMap[J].IETFFamily;
794 if (Proto == 0)
795 return _error->Error(_("Unknown address family %u (AF_*)"),
796 ((struct sockaddr *)&Addr)->sa_family);
797
798 // Send the EPRT command
799 unsigned int Tag;
800 string Msg;
801 if (WriteMsg(Tag,Msg,"EPRT |%u|%s|%s|",Proto,Name,Service) == false)
802 return false;
803 if (Tag >= 400)
804 return _error->Error(_("EPRT failed, server said: %s"),Msg.c_str());
805 return true;
806 }
807 /*}}}*/
808 // FTPConn::Finalize - Complete the Data connection /*{{{*/
809 // ---------------------------------------------------------------------
810 /* If the connection is in port mode this waits for the other end to hook
811 up to us. */
812 bool FTPConn::Finalize()
813 {
814 // Passive mode? Do nothing
815 if (PasvAddr != 0)
816 return true;
817
818 // Close any old socket..
819 close(DataFd);
820 DataFd = -1;
821
822 // Wait for someone to connect..
823 if (WaitFd(DataListenFd,false,TimeOut) == false)
824 return _error->Error(_("Data socket connect timed out"));
825
826 // Accept the connection
827 struct sockaddr_in Addr;
828 socklen_t Len = sizeof(Addr);
829 DataFd = accept(DataListenFd,(struct sockaddr *)&Addr,&Len);
830 if (DataFd < 0)
831 return _error->Errno("accept",_("Unable to accept connection"));
832
833 close(DataListenFd);
834 DataListenFd = -1;
835
836 return true;
837 }
838 /*}}}*/
839 // FTPConn::Get - Get a file /*{{{*/
840 // ---------------------------------------------------------------------
841 /* This opens a data connection, sends REST and RETR and then
842 transfers the file over. */
843 bool FTPConn::Get(const char *Path,FileFd &To,unsigned long Resume,
844 Hashes &Hash,bool &Missing)
845 {
846 Missing = false;
847 if (CreateDataFd() == false)
848 return false;
849
850 unsigned int Tag;
851 string Msg;
852 if (Resume != 0)
853 {
854 if (WriteMsg(Tag,Msg,"REST %u",Resume) == false)
855 return false;
856 if (Tag >= 400)
857 Resume = 0;
858 }
859
860 if (To.Truncate(Resume) == false)
861 return false;
862
863 if (To.Seek(0) == false)
864 return false;
865
866 if (Resume != 0)
867 {
868 if (Hash.AddFD(To.Fd(),Resume) == false)
869 {
870 _error->Errno("read",_("Problem hashing file"));
871 return false;
872 }
873 }
874
875 // Send the get command
876 if (WriteMsg(Tag,Msg,"RETR %s",Path) == false)
877 return false;
878
879 if (Tag >= 400)
880 {
881 if (Tag == 550)
882 Missing = true;
883 return _error->Error(_("Unable to fetch file, server said '%s'"),Msg.c_str());
884 }
885
886 // Finish off the data connection
887 if (Finalize() == false)
888 return false;
889
890 // Copy loop
891 unsigned char Buffer[4096];
892 while (1)
893 {
894 // Wait for some data..
895 if (WaitFd(DataFd,false,TimeOut) == false)
896 {
897 Close();
898 return _error->Error(_("Data socket timed out"));
899 }
900
901 // Read the data..
902 int Res = read(DataFd,Buffer,sizeof(Buffer));
903 if (Res == 0)
904 break;
905 if (Res < 0)
906 {
907 if (errno == EAGAIN)
908 continue;
909 break;
910 }
911
912 Hash.Add(Buffer,Res);
913 if (To.Write(Buffer,Res) == false)
914 {
915 Close();
916 return false;
917 }
918 }
919
920 // All done
921 close(DataFd);
922 DataFd = -1;
923
924 // Read the closing message from the server
925 if (ReadResp(Tag,Msg) == false)
926 return false;
927 if (Tag >= 400)
928 return _error->Error(_("Data transfer failed, server said '%s'"),Msg.c_str());
929 return true;
930 }
931 /*}}}*/
932
933 // FtpMethod::FtpMethod - Constructor /*{{{*/
934 // ---------------------------------------------------------------------
935 /* */
936 FtpMethod::FtpMethod() : pkgAcqMethod("1.0",SendConfig)
937 {
938 signal(SIGTERM,SigTerm);
939 signal(SIGINT,SigTerm);
940
941 Server = 0;
942 FailFd = -1;
943 }
944 /*}}}*/
945 // FtpMethod::SigTerm - Handle a fatal signal /*{{{*/
946 // ---------------------------------------------------------------------
947 /* This closes and timestamps the open file. This is neccessary to get
948 resume behavoir on user abort */
949 void FtpMethod::SigTerm(int)
950 {
951 if (FailFd == -1)
952 _exit(100);
953 close(FailFd);
954
955 // Timestamp
956 struct utimbuf UBuf;
957 UBuf.actime = FailTime;
958 UBuf.modtime = FailTime;
959 utime(FailFile.c_str(),&UBuf);
960
961 _exit(100);
962 }
963 /*}}}*/
964 // FtpMethod::Configuration - Handle a configuration message /*{{{*/
965 // ---------------------------------------------------------------------
966 /* We stash the desired pipeline depth */
967 bool FtpMethod::Configuration(string Message)
968 {
969 if (pkgAcqMethod::Configuration(Message) == false)
970 return false;
971
972 TimeOut = _config->FindI("Acquire::Ftp::Timeout",TimeOut);
973 return true;
974 }
975 /*}}}*/
976 // FtpMethod::Fetch - Fetch a file /*{{{*/
977 // ---------------------------------------------------------------------
978 /* Fetch a single file, called by the base class.. */
979 bool FtpMethod::Fetch(FetchItem *Itm)
980 {
981 URI Get = Itm->Uri;
982 const char *File = Get.Path.c_str();
983 FetchResult Res;
984 Res.Filename = Itm->DestFile;
985 Res.IMSHit = false;
986
987 maybe_add_auth (Get, _config->FindFile("Dir::Etc::netrc"));
988
989 // Connect to the server
990 if (Server == 0 || Server->Comp(Get) == false)
991 {
992 delete Server;
993 Server = new FTPConn(Get);
994 }
995
996 // Could not connect is a transient error..
997 if (Server->Open(this) == false)
998 {
999 Server->Close();
1000 Fail(true);
1001 return true;
1002 }
1003
1004 // Get the files information
1005 Status(_("Query"));
1006 unsigned long Size;
1007 if (Server->Size(File,Size) == false ||
1008 Server->ModTime(File,FailTime) == false)
1009 {
1010 Fail(true);
1011 return true;
1012 }
1013 Res.Size = Size;
1014
1015 // See if it is an IMS hit
1016 if (Itm->LastModified == FailTime)
1017 {
1018 Res.Size = 0;
1019 Res.IMSHit = true;
1020 URIDone(Res);
1021 return true;
1022 }
1023
1024 // See if the file exists
1025 struct stat Buf;
1026 if (stat(Itm->DestFile.c_str(),&Buf) == 0)
1027 {
1028 if (Size == (unsigned)Buf.st_size && FailTime == Buf.st_mtime)
1029 {
1030 Res.Size = Buf.st_size;
1031 Res.LastModified = Buf.st_mtime;
1032 Res.ResumePoint = Buf.st_size;
1033 URIDone(Res);
1034 return true;
1035 }
1036
1037 // Resume?
1038 if (FailTime == Buf.st_mtime && Size > (unsigned)Buf.st_size)
1039 Res.ResumePoint = Buf.st_size;
1040 }
1041
1042 // Open the file
1043 Hashes Hash;
1044 {
1045 FileFd Fd(Itm->DestFile,FileFd::WriteAny);
1046 if (_error->PendingError() == true)
1047 return false;
1048
1049 URIStart(Res);
1050
1051 FailFile = Itm->DestFile;
1052 FailFile.c_str(); // Make sure we dont do a malloc in the signal handler
1053 FailFd = Fd.Fd();
1054
1055 bool Missing;
1056 if (Server->Get(File,Fd,Res.ResumePoint,Hash,Missing) == false)
1057 {
1058 Fd.Close();
1059
1060 // Timestamp
1061 struct utimbuf UBuf;
1062 UBuf.actime = FailTime;
1063 UBuf.modtime = FailTime;
1064 utime(FailFile.c_str(),&UBuf);
1065
1066 // If the file is missing we hard fail and delete the destfile
1067 // otherwise transient fail
1068 if (Missing == true) {
1069 unlink(FailFile.c_str());
1070 return false;
1071 }
1072 Fail(true);
1073 return true;
1074 }
1075
1076 Res.Size = Fd.Size();
1077 }
1078
1079 Res.LastModified = FailTime;
1080 Res.TakeHashes(Hash);
1081
1082 // Timestamp
1083 struct utimbuf UBuf;
1084 UBuf.actime = FailTime;
1085 UBuf.modtime = FailTime;
1086 utime(Queue->DestFile.c_str(),&UBuf);
1087 FailFd = -1;
1088
1089 URIDone(Res);
1090
1091 return true;
1092 }
1093 /*}}}*/
1094
1095 int main(int argc,const char *argv[])
1096 {
1097 setlocale(LC_ALL, "");
1098
1099 /* See if we should be come the http client - we do this for http
1100 proxy urls */
1101 if (getenv("ftp_proxy") != 0)
1102 {
1103 URI Proxy = string(getenv("ftp_proxy"));
1104
1105 // Run the HTTP method
1106 if (Proxy.Access == "http")
1107 {
1108 // Copy over the environment setting
1109 char S[300];
1110 snprintf(S,sizeof(S),"http_proxy=%s",getenv("ftp_proxy"));
1111 putenv(S);
1112 putenv((char *)"no_proxy=");
1113
1114 // Run the http method
1115 string Path = flNotFile(argv[0]) + "http";
1116 execl(Path.c_str(),Path.c_str(),(char *)NULL);
1117 cerr << _("Unable to invoke ") << Path << endl;
1118 exit(100);
1119 }
1120 }
1121
1122 FtpMethod Mth;
1123
1124 return Mth.Run();
1125 }