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