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