]> git.saurik.com Git - apt.git/blob - methods/ftp.cc
Updated error messages to reflect host/ip
[apt.git] / methods / ftp.cc
1 // -*- mode: cpp; mode: fold -*-
2 // Description /*{{{*/
3 // $Id: ftp.cc,v 1.12 1999/05/28 07:04:45 jgg Exp $
4 /* ######################################################################
5
6 HTTP 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/md5.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
32 // Internet stuff
33 #include <netinet/in.h>
34 #include <sys/socket.h>
35 #include <arpa/inet.h>
36 #include <netdb.h>
37
38 #include "rfc2553emu.h"
39 #include "ftp.h"
40 /*}}}*/
41
42 unsigned long TimeOut = 120;
43 URI Proxy;
44 string FtpMethod::FailFile;
45 int FtpMethod::FailFd = -1;
46 time_t FtpMethod::FailTime = 0;
47
48 // FTPConn::FTPConn - Constructor /*{{{*/
49 // ---------------------------------------------------------------------
50 /* */
51 FTPConn::FTPConn(URI Srv) : Len(0), ServerFd(-1), DataFd(-1),
52 DataListenFd(-1), ServerName(Srv)
53 {
54 Debug = _config->FindB("Debug::Acquire::Ftp",false);
55 memset(&PasvAddr,0,sizeof(PasvAddr));
56 }
57 /*}}}*/
58 // FTPConn::~FTPConn - Destructor /*{{{*/
59 // ---------------------------------------------------------------------
60 /* */
61 FTPConn::~FTPConn()
62 {
63 Close();
64 }
65 /*}}}*/
66 // FTPConn::Close - Close down the connection /*{{{*/
67 // ---------------------------------------------------------------------
68 /* Just tear down the socket and data socket */
69 void FTPConn::Close()
70 {
71 close(ServerFd);
72 ServerFd = -1;
73 close(DataFd);
74 DataFd = -1;
75 close(DataListenFd);
76 DataListenFd = -1;
77 memset(&PasvAddr,0,sizeof(PasvAddr));
78 }
79 /*}}}*/
80 // FTPConn::Open - Open a new connection /*{{{*/
81 // ---------------------------------------------------------------------
82 /* Connect to the server using a non-blocking connection and perform a
83 login. */
84 string LastHost;
85 int LastPort = 0;
86 struct addrinfo *LastHostAddr = 0;
87 bool FTPConn::Open(pkgAcqMethod *Owner)
88 {
89 // Use the already open connection if possible.
90 if (ServerFd != -1)
91 return true;
92
93 Close();
94
95 // Determine the proxy setting
96 if (getenv("ftp_proxy") == 0)
97 {
98 string DefProxy = _config->Find("Acquire::ftp::Proxy");
99 string SpecificProxy = _config->Find("Acquire::ftp::Proxy::" + ServerName.Host);
100 if (SpecificProxy.empty() == false)
101 {
102 if (SpecificProxy == "DIRECT")
103 Proxy = "";
104 else
105 Proxy = SpecificProxy;
106 }
107 else
108 Proxy = DefProxy;
109 }
110 else
111 Proxy = getenv("ftp_proxy");
112
113 // Determine what host and port to use based on the proxy settings
114 int Port = 0;
115 string Host;
116 if (Proxy.empty() == true)
117 {
118 if (ServerName.Port != 0)
119 Port = ServerName.Port;
120 Host = ServerName.Host;
121 }
122 else
123 {
124 if (Proxy.Port != 0)
125 Port = Proxy.Port;
126 Host = Proxy.Host;
127 }
128
129 /* We used a cached address record.. Yes this is against the spec but
130 the way we have setup our rotating dns suggests that this is more
131 sensible */
132 if (LastHost != Host || LastPort != Port)
133 {
134 Owner->Status("Connecting to %s",Host.c_str());
135
136 // Lookup the host
137 char S[30] = "ftp";
138 if (Port != 0)
139 snprintf(S,sizeof(S),"%u",Port);
140
141 // Free the old address structure
142 if (LastHostAddr != 0)
143 {
144 freeaddrinfo(LastHostAddr);
145 LastHostAddr = 0;
146 }
147
148 // We only understand SOCK_STREAM sockets.
149 struct addrinfo Hints;
150 memset(&Hints,0,sizeof(Hints));
151 Hints.ai_socktype = SOCK_STREAM;
152
153 // Resolve both the host and service simultaneously
154 if (getaddrinfo(Host.c_str(),S,&Hints,&LastHostAddr) != 0 ||
155 LastHostAddr == 0)
156 return _error->Error("Could not resolve '%s'",Host.c_str());
157
158 LastHost = Host;
159 LastPort = Port;
160 }
161
162 // Get the printable IP address
163 char Name[NI_MAXHOST];
164 Name[0] = 0;
165 getnameinfo(LastHostAddr->ai_addr,LastHostAddr->ai_addrlen,
166 Name,sizeof(Name),0,0,NI_NUMERICHOST);
167 Owner->Status("Connecting to %s (%s)",Host.c_str(),Name);
168
169 // Get a socket
170 if ((ServerFd = socket(LastHostAddr->ai_family,LastHostAddr->ai_socktype,
171 LastHostAddr->ai_protocol)) < 0)
172 return _error->Errno("socket","Could not create a socket");
173 SetNonBlock(ServerFd,true);
174 if (connect(ServerFd,LastHostAddr->ai_addr,LastHostAddr->ai_addrlen) < 0 &&
175 errno != EINPROGRESS)
176 return _error->Errno("connect","Cannot initiate the connection "
177 "to %s (%s).",Host.c_str(),Name);
178 Peer = *((struct sockaddr_in *)LastHostAddr->ai_addr);
179
180 /* This implements a timeout for connect by opening the connection
181 nonblocking */
182 if (WaitFd(ServerFd,true,TimeOut) == false)
183 return _error->Error("Could not connect to %s (%s), "
184 "connection timed out",Host.c_str(),Name);
185 unsigned int Err;
186 unsigned int Len = sizeof(Err);
187 if (getsockopt(ServerFd,SOL_SOCKET,SO_ERROR,&Err,&Len) != 0)
188 return _error->Errno("getsockopt","Failed");
189 if (Err != 0)
190 return _error->Error("Could not connect to %s (%s).",Host.c_str(),Name);
191
192 Owner->Status("Logging in");
193 return Login();
194 }
195 /*}}}*/
196 // FTPConn::Login - Login to the remote server /*{{{*/
197 // ---------------------------------------------------------------------
198 /* This performs both normal login and proxy login using a simples script
199 stored in the config file. */
200 bool FTPConn::Login()
201 {
202 unsigned int Tag;
203 string Msg;
204
205 // Setup the variables needed for authentication
206 string User = "anonymous";
207 string Pass = "apt_get_ftp_2.0@debian.linux.user";
208
209 // Fill in the user/pass
210 if (ServerName.User.empty() == false)
211 User = ServerName.User;
212 if (ServerName.Password.empty() == false)
213 Pass = ServerName.Password;
214
215 // Perform simple login
216 if (Proxy.empty() == true)
217 {
218 // Read the initial response
219 if (ReadResp(Tag,Msg) == false)
220 return false;
221 if (Tag >= 400)
222 return _error->Error("Server refused our connection and said: %s",Msg.c_str());
223
224 // Send the user
225 if (WriteMsg(Tag,Msg,"USER %s",User.c_str()) == false)
226 return false;
227 if (Tag >= 400)
228 return _error->Error("USER failed, server said: %s",Msg.c_str());
229
230 // Send the Password
231 if (WriteMsg(Tag,Msg,"PASS %s",Pass.c_str()) == false)
232 return false;
233 if (Tag >= 400)
234 return _error->Error("PASS failed, server said: %s",Msg.c_str());
235
236 // Enter passive mode
237 if (_config->Exists("Acquire::FTP::Passive::" + ServerName.Host) == true)
238 TryPassive = _config->FindB("Acquire::FTP::Passive::" + ServerName.Host,true);
239 else
240 TryPassive = _config->FindB("Acquire::FTP::Passive",true);
241 }
242 else
243 {
244 // Read the initial response
245 if (ReadResp(Tag,Msg) == false)
246 return false;
247 if (Tag >= 400)
248 return _error->Error("Server refused our connection and said: %s",Msg.c_str());
249
250 // Perform proxy script execution
251 Configuration::Item const *Opts = _config->Tree("Acquire::ftp::ProxyLogin");
252 if (Opts == 0 || Opts->Child == 0)
253 return _error->Error("A proxy server was specified but no login "
254 "script, Acquire::ftp::ProxyLogin is empty.");
255 Opts = Opts->Child;
256
257 // Iterate over the entire login script
258 for (; Opts != 0; Opts = Opts->Next)
259 {
260 if (Opts->Value.empty() == true)
261 continue;
262
263 // Substitute the variables into the command
264 char SitePort[20];
265 if (ServerName.Port != 0)
266 sprintf(SitePort,"%u",ServerName.Port);
267 else
268 strcpy(SitePort,"21");
269 string Tmp = Opts->Value;
270 Tmp = SubstVar(Tmp,"$(PROXY_USER)",Proxy.User);
271 Tmp = SubstVar(Tmp,"$(PROXY_PASS)",Proxy.Password);
272 Tmp = SubstVar(Tmp,"$(SITE_USER)",User);
273 Tmp = SubstVar(Tmp,"$(SITE_PASS)",Pass);
274 Tmp = SubstVar(Tmp,"$(SITE_PORT)",SitePort);
275 Tmp = SubstVar(Tmp,"$(SITE)",ServerName.Host);
276
277 // Send the command
278 if (WriteMsg(Tag,Msg,"%s",Tmp.c_str()) == false)
279 return false;
280 if (Tag >= 400)
281 return _error->Error("Login script command '%s' failed, server said: %s",Tmp.c_str(),Msg.c_str());
282 }
283
284 // Enter passive mode
285 TryPassive = false;
286 if (_config->Exists("Acquire::FTP::Passive::" + ServerName.Host) == true)
287 TryPassive = _config->FindB("Acquire::FTP::Passive::" + ServerName.Host,true);
288 else
289 {
290 if (_config->Exists("Acquire::FTP::Proxy::Passive") == true)
291 TryPassive = _config->FindB("Acquire::FTP::Proxy::Passive",true);
292 else
293 TryPassive = _config->FindB("Acquire::FTP::Passive",true);
294 }
295 }
296
297 // Binary mode
298 if (WriteMsg(Tag,Msg,"TYPE I") == false)
299 return false;
300 if (Tag >= 400)
301 return _error->Error("TYPE failed, server said: %s",Msg.c_str());
302
303 return true;
304 }
305 /*}}}*/
306 // FTPConn::ReadLine - Read a line from the server /*{{{*/
307 // ---------------------------------------------------------------------
308 /* This performs a very simple buffered read. */
309 bool FTPConn::ReadLine(string &Text)
310 {
311 if (ServerFd == -1)
312 return false;
313
314 // Suck in a line
315 while (Len < sizeof(Buffer))
316 {
317 // Scan the buffer for a new line
318 for (unsigned int I = 0; I != Len; I++)
319 {
320 // Escape some special chars
321 if (Buffer[I] == 0)
322 Buffer[I] = '?';
323
324 // End of line?
325 if (Buffer[I] != '\n')
326 continue;
327
328 I++;
329 Text = string(Buffer,I);
330 memmove(Buffer,Buffer+I,Len - I);
331 Len -= I;
332 return true;
333 }
334
335 // Wait for some data..
336 if (WaitFd(ServerFd,false,TimeOut) == false)
337 {
338 Close();
339 return _error->Error("Connection timeout");
340 }
341
342 // Suck it back
343 int Res = read(ServerFd,Buffer + Len,sizeof(Buffer) - Len);
344 if (Res <= 0)
345 {
346 Close();
347 return _error->Errno("read","Read error");
348 }
349 Len += Res;
350 }
351
352 return _error->Error("A response overflowed the buffer.");
353 }
354 /*}}}*/
355 // FTPConn::ReadResp - Read a full response from the server /*{{{*/
356 // ---------------------------------------------------------------------
357 /* This reads a reply code from the server, it handles both p */
358 bool FTPConn::ReadResp(unsigned int &Ret,string &Text)
359 {
360 // Grab the first line of the response
361 string Msg;
362 if (ReadLine(Msg) == false)
363 return false;
364
365 // Get the ID code
366 char *End;
367 Ret = strtol(Msg.c_str(),&End,10);
368 if (End - Msg.c_str() != 3)
369 return _error->Error("Protocol corruption");
370
371 // All done ?
372 Text = Msg.c_str()+4;
373 if (*End == ' ')
374 {
375 if (Debug == true)
376 cerr << "<- '" << QuoteString(Text,"") << "'" << endl;
377 return true;
378 }
379
380 if (*End != '-')
381 return _error->Error("Protocol corruption");
382
383 /* Okay, here we do the continued message trick. This is foolish, but
384 proftpd follows the protocol as specified and wu-ftpd doesn't, so
385 we filter. I wonder how many clients break if you use proftpd and
386 put a '- in the 3rd spot in the message? */
387 char Leader[4];
388 strncpy(Leader,Msg.c_str(),3);
389 Leader[3] = 0;
390 while (ReadLine(Msg) == true)
391 {
392 // Short, it must be using RFC continuation..
393 if (Msg.length() < 4)
394 {
395 Text += Msg;
396 continue;
397 }
398
399 // Oops, finished
400 if (strncmp(Msg.c_str(),Leader,3) == 0 && Msg[3] == ' ')
401 {
402 Text += Msg.c_str()+4;
403 break;
404 }
405
406 // This message has the wu-ftpd style reply code prefixed
407 if (strncmp(Msg.c_str(),Leader,3) == 0 && Msg[3] == '-')
408 {
409 Text += Msg.c_str()+4;
410 continue;
411 }
412
413 // Must be RFC style prefixing
414 Text += Msg;
415 }
416
417 if (Debug == true && _error->PendingError() == false)
418 cerr << "<- '" << QuoteString(Text,"") << "'" << endl;
419
420 return !_error->PendingError();
421 }
422 /*}}}*/
423 // FTPConn::WriteMsg - Send a message to the server /*{{{*/
424 // ---------------------------------------------------------------------
425 /* Simple printf like function.. */
426 bool FTPConn::WriteMsg(unsigned int &Ret,string &Text,const char *Fmt,...)
427 {
428 va_list args;
429 va_start(args,Fmt);
430
431 // sprintf the description
432 char S[400];
433 vsnprintf(S,sizeof(S) - 4,Fmt,args);
434 strcat(S,"\r\n");
435
436 if (Debug == true)
437 cerr << "-> '" << QuoteString(S,"") << "'" << endl;
438
439 // Send it off
440 unsigned long Len = strlen(S);
441 unsigned long Start = 0;
442 while (Len != 0)
443 {
444 if (WaitFd(ServerFd,true,TimeOut) == false)
445 {
446 Close();
447 return _error->Error("Connection timeout");
448 }
449
450 int Res = write(ServerFd,S + Start,Len);
451 if (Res <= 0)
452 {
453 Close();
454 return _error->Errno("write","Write Error");
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 Borrowed mostly from lftp. We have to enter passive mode every time
469 we make a data connection :| */
470 bool FTPConn::GoPasv()
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 {
482 memset(&PasvAddr,0,sizeof(PasvAddr));
483 return true;
484 }
485
486 // Scan it
487 unsigned a0,a1,a2,a3,p0,p1;
488 if (sscanf(Msg.c_str() + Pos,"(%u,%u,%u,%u,%u,%u)",&a0,&a1,&a2,&a3,&p0,&p1) != 6)
489 {
490 memset(&PasvAddr,0,sizeof(PasvAddr));
491 return true;
492 }
493
494 // lftp used this horrid byte order manipulation.. Ik.
495 PasvAddr.sin_family = AF_INET;
496 unsigned char *a;
497 unsigned char *p;
498 a = (unsigned char *)&PasvAddr.sin_addr;
499 p = (unsigned char *)&PasvAddr.sin_port;
500
501 // Some evil servers return 0 to mean their addr
502 if (a0 == 0 && a1 == 0 && a2 == 0 && a3 == 0)
503 {
504 PasvAddr.sin_addr = Peer.sin_addr;
505 }
506 else
507 {
508 a[0] = a0;
509 a[1] = a1;
510 a[2] = a2;
511 a[3] = a3;
512 }
513
514 p[0] = p0;
515 p[1] = p1;
516
517 return true;
518 }
519 /*}}}*/
520 // FTPConn::Size - Return the size of a file /*{{{*/
521 // ---------------------------------------------------------------------
522 /* Grab the file size from the server, 0 means no size or empty file */
523 bool FTPConn::Size(const char *Path,unsigned long &Size)
524 {
525 // Query the size
526 unsigned int Tag;
527 string Msg;
528 Size = 0;
529 if (WriteMsg(Tag,Msg,"SIZE %s",Path) == false)
530 return false;
531
532 char *End;
533 Size = strtol(Msg.c_str(),&End,10);
534 if (Tag >= 400 || End == Msg.c_str())
535 Size = 0;
536 return true;
537 }
538 /*}}}*/
539 // FTPConn::ModTime - Return the modification time of the file /*{{{*/
540 // ---------------------------------------------------------------------
541 /* Like Size no error is returned if the command is not supported. If the
542 command fails then time is set to the current time of day to fool
543 date checks. */
544 bool FTPConn::ModTime(const char *Path, time_t &Time)
545 {
546 Time = time(&Time);
547
548 // Query the mod time
549 unsigned int Tag;
550 string Msg;
551 if (WriteMsg(Tag,Msg,"MDTM %s",Path) == false)
552 return false;
553 if (Tag >= 400 || Msg.empty() == true || isdigit(Msg[0]) == 0)
554 return true;
555
556 // Parse it
557 struct tm tm;
558 memset(&tm,0,sizeof(tm));
559 if (sscanf(Msg.c_str(),"%4d%2d%2d%2d%2d%2d",&tm.tm_year,&tm.tm_mon,
560 &tm.tm_mday,&tm.tm_hour,&tm.tm_min,&tm.tm_sec) != 6)
561 return true;
562
563 tm.tm_year -= 1900;
564 tm.tm_mon--;
565
566 /* We use timegm from the GNU C library, libapt-pkg will provide this
567 symbol if it does not exist */
568 Time = timegm(&tm);
569 return true;
570 }
571 /*}}}*/
572 // FTPConn::CreateDataFd - Get a data connection /*{{{*/
573 // ---------------------------------------------------------------------
574 /* Create the data connection. Call FinalizeDataFd after this though.. */
575 bool FTPConn::CreateDataFd()
576 {
577 close(DataFd);
578 DataFd = -1;
579
580 // Attempt to enter passive mode.
581 if (TryPassive == true)
582 {
583 if (GoPasv() == false)
584 return false;
585
586 // Oops, didn't work out, don't bother trying again.
587 if (PasvAddr.sin_port == 0)
588 TryPassive = false;
589 }
590
591 // Passive mode?
592 if (PasvAddr.sin_port != 0)
593 {
594 // Get a socket
595 if ((DataFd = socket(AF_INET,SOCK_STREAM,0)) < 0)
596 return _error->Errno("socket","Could not create a socket");
597
598 // Connect to the server
599 SetNonBlock(DataFd,true);
600 if (connect(DataFd,(sockaddr *)&PasvAddr,sizeof(PasvAddr)) < 0 &&
601 errno != EINPROGRESS)
602 return _error->Errno("socket","Could not create a socket");
603
604 /* This implements a timeout for connect by opening the connection
605 nonblocking */
606 if (WaitFd(ServerFd,true,TimeOut) == false)
607 return _error->Error("Could not connect data socket, connection timed out");
608 unsigned int Err;
609 unsigned int Len = sizeof(Err);
610 if (getsockopt(ServerFd,SOL_SOCKET,SO_ERROR,&Err,&Len) != 0)
611 return _error->Errno("getsockopt","Failed");
612 if (Err != 0)
613 return _error->Error("Could not connect.");
614
615 return true;
616 }
617
618 // Port mode :<
619 close(DataListenFd);
620 DataListenFd = -1;
621
622 // Get a socket
623 if ((DataListenFd = socket(AF_INET,SOCK_STREAM,0)) < 0)
624 return _error->Errno("socket","Could not create a socket");
625
626 // Bind and listen
627 sockaddr_in Addr;
628 memset(&Addr,0,sizeof(Addr));
629 if (bind(DataListenFd,(sockaddr *)&Addr,sizeof(Addr)) < 0)
630 return _error->Errno("bind","Could not bind a socket");
631 if (listen(DataListenFd,1) < 0)
632 return _error->Errno("listen","Could not listen on the socket");
633 SetNonBlock(DataListenFd,true);
634
635 // Determine the name to send to the remote
636 sockaddr_in Addr2;
637 socklen_t Jnk = sizeof(Addr);
638 if (getsockname(DataListenFd,(sockaddr *)&Addr,&Jnk) < 0)
639 return _error->Errno("getsockname","Could not determine the socket's name");
640 Jnk = sizeof(Addr2);
641 if (getsockname(ServerFd,(sockaddr *)&Addr2,&Jnk) < 0)
642 return _error->Errno("getsockname","Could not determine the socket's name");
643
644 // This bit ripped from qftp
645 unsigned long badr = ntohl(*(unsigned long *)&Addr2.sin_addr);
646 unsigned long bp = ntohs(Addr.sin_port);
647
648 // Send the port command
649 unsigned int Tag;
650 string Msg;
651 if (WriteMsg(Tag,Msg,"PORT %d,%d,%d,%d,%d,%d",
652 (int) (badr >> 24) & 0xff, (int) (badr >> 16) & 0xff,
653 (int) (badr >> 8) & 0xff, (int) badr & 0xff,
654 (int) (bp >> 8) & 0xff, (int) bp & 0xff) == false)
655 return false;
656 if (Tag >= 400)
657 return _error->Error("Unable to send port command");
658
659 return true;
660 }
661 /*}}}*/
662 // FTPConn::Finalize - Complete the Data connection /*{{{*/
663 // ---------------------------------------------------------------------
664 /* If the connection is in port mode this waits for the other end to hook
665 up to us. */
666 bool FTPConn::Finalize()
667 {
668 // Passive mode? Do nothing
669 if (PasvAddr.sin_port != 0)
670 return true;
671
672 // Close any old socket..
673 close(DataFd);
674 DataFd = -1;
675
676 // Wait for someone to connect..
677 if (WaitFd(DataListenFd,false,TimeOut) == false)
678 return _error->Error("Data socket connect timed out");
679
680 // Accept the connection
681 struct sockaddr_in Addr;
682 socklen_t Len = sizeof(Addr);
683 DataFd = accept(DataListenFd,(struct sockaddr *)&Addr,&Len);
684 if (DataFd < 0)
685 return _error->Errno("accept","Unable to accept connection");
686
687 close(DataListenFd);
688 DataListenFd = -1;
689
690 return true;
691 }
692 /*}}}*/
693 // FTPConn::Get - Get a file /*{{{*/
694 // ---------------------------------------------------------------------
695 /* This opens a data connection, sends REST and RETR and then
696 transfers the file over. */
697 bool FTPConn::Get(const char *Path,FileFd &To,unsigned long Resume,
698 MD5Summation &MD5,bool &Missing)
699 {
700 Missing = false;
701 if (CreateDataFd() == false)
702 return false;
703
704 unsigned int Tag;
705 string Msg;
706 if (Resume != 0)
707 {
708 if (WriteMsg(Tag,Msg,"REST %u",Resume) == false)
709 return false;
710 if (Tag >= 400)
711 Resume = 0;
712 }
713
714 if (To.Truncate(Resume) == false)
715 return false;
716
717 if (To.Seek(0) == false)
718 return false;
719
720 if (Resume != 0)
721 {
722 if (MD5.AddFD(To.Fd(),Resume) == false)
723 {
724 _error->Errno("read","Problem hashing file");
725 return false;
726 }
727 }
728
729 // Send the get command
730 if (WriteMsg(Tag,Msg,"RETR %s",Path) == false)
731 return false;
732
733 if (Tag >= 400)
734 {
735 if (Tag == 550)
736 Missing = true;
737 return _error->Error("Unable to fetch file, server said '%s'",Msg.c_str());
738 }
739
740 // Finish off the data connection
741 if (Finalize() == false)
742 return false;
743
744 // Copy loop
745 unsigned char Buffer[4096];
746 while (1)
747 {
748 // Wait for some data..
749 if (WaitFd(DataFd,false,TimeOut) == false)
750 {
751 Close();
752 return _error->Error("Data socket timed out");
753 }
754
755 // Read the data..
756 int Res = read(DataFd,Buffer,sizeof(Buffer));
757 if (Res == 0)
758 break;
759 if (Res < 0)
760 {
761 if (errno == EAGAIN)
762 continue;
763 break;
764 }
765
766 MD5.Add(Buffer,Res);
767 if (To.Write(Buffer,Res) == false)
768 {
769 Close();
770 return false;
771 }
772 }
773
774 // All done
775 close(DataFd);
776 DataFd = -1;
777
778 // Read the closing message from the server
779 if (ReadResp(Tag,Msg) == false)
780 return false;
781 if (Tag >= 400)
782 return _error->Error("Data transfer failed, server said '%s'",Msg.c_str());
783 return true;
784 }
785 /*}}}*/
786
787 // FtpMethod::FtpMethod - Constructor /*{{{*/
788 // ---------------------------------------------------------------------
789 /* */
790 FtpMethod::FtpMethod() : pkgAcqMethod("1.0",SendConfig)
791 {
792 signal(SIGTERM,SigTerm);
793 signal(SIGINT,SigTerm);
794
795 Server = 0;
796 FailFd = -1;
797 }
798 /*}}}*/
799 // FtpMethod::SigTerm - Handle a fatal signal /*{{{*/
800 // ---------------------------------------------------------------------
801 /* This closes and timestamps the open file. This is neccessary to get
802 resume behavoir on user abort */
803 void FtpMethod::SigTerm(int)
804 {
805 if (FailFd == -1)
806 exit(100);
807 close(FailFd);
808
809 // Timestamp
810 struct utimbuf UBuf;
811 UBuf.actime = FailTime;
812 UBuf.modtime = FailTime;
813 utime(FailFile.c_str(),&UBuf);
814
815 exit(100);
816 }
817 /*}}}*/
818 // FtpMethod::Configuration - Handle a configuration message /*{{{*/
819 // ---------------------------------------------------------------------
820 /* We stash the desired pipeline depth */
821 bool FtpMethod::Configuration(string Message)
822 {
823 if (pkgAcqMethod::Configuration(Message) == false)
824 return false;
825
826 TimeOut = _config->FindI("Acquire::Ftp::Timeout",TimeOut);
827 return true;
828 }
829 /*}}}*/
830 // FtpMethod::Fetch - Fetch a file /*{{{*/
831 // ---------------------------------------------------------------------
832 /* Fetch a single file, called by the base class.. */
833 bool FtpMethod::Fetch(FetchItem *Itm)
834 {
835 URI Get = Itm->Uri;
836 const char *File = Get.Path.c_str();
837 FetchResult Res;
838 Res.Filename = Itm->DestFile;
839 Res.IMSHit = false;
840
841 // Connect to the server
842 if (Server == 0 || Server->Comp(Get) == false)
843 {
844 delete Server;
845 Server = new FTPConn(Get);
846 }
847
848 // Could not connect is a transient error..
849 if (Server->Open(this) == false)
850 {
851 Server->Close();
852 Fail(true);
853 return true;
854 }
855
856 // Get the files information
857 Status("Query");
858 unsigned long Size;
859 if (Server->Size(File,Size) == false ||
860 Server->ModTime(File,FailTime) == false)
861 {
862 Fail(true);
863 return true;
864 }
865 Res.Size = Size;
866
867 // See if it is an IMS hit
868 if (Itm->LastModified == FailTime)
869 {
870 Res.Size = 0;
871 Res.IMSHit = true;
872 URIDone(Res);
873 return true;
874 }
875
876 // See if the file exists
877 struct stat Buf;
878 if (stat(Itm->DestFile.c_str(),&Buf) == 0)
879 {
880 if (Size == (unsigned)Buf.st_size && FailTime == Buf.st_mtime)
881 {
882 Res.Size = Buf.st_size;
883 Res.LastModified = Buf.st_mtime;
884 URIDone(Res);
885 return true;
886 }
887
888 // Resume?
889 if (FailTime == Buf.st_mtime && Size > (unsigned)Buf.st_size)
890 Res.ResumePoint = Buf.st_size;
891 }
892
893 // Open the file
894 MD5Summation MD5;
895 {
896 FileFd Fd(Itm->DestFile,FileFd::WriteAny);
897 if (_error->PendingError() == true)
898 return false;
899
900 URIStart(Res);
901
902 FailFile = Itm->DestFile;
903 FailFile.c_str(); // Make sure we dont do a malloc in the signal handler
904 FailFd = Fd.Fd();
905
906 bool Missing;
907 if (Server->Get(File,Fd,Res.ResumePoint,MD5,Missing) == false)
908 {
909 // If the file is missing we hard fail otherwise transient fail
910 if (Missing == true)
911 return false;
912 Fail(true);
913 return true;
914 }
915
916 Res.Size = Fd.Size();
917 }
918
919 Res.LastModified = FailTime;
920 Res.MD5Sum = MD5.Result();
921
922 // Timestamp
923 struct utimbuf UBuf;
924 time(&UBuf.actime);
925 UBuf.actime = FailTime;
926 UBuf.modtime = FailTime;
927 utime(Queue->DestFile.c_str(),&UBuf);
928 FailFd = -1;
929
930 URIDone(Res);
931
932 return true;
933 }
934 /*}}}*/
935
936 int main(int argc,const char *argv[])
937 {
938 /* See if we should be come the http client - we do this for http
939 proxy urls */
940 if (getenv("ftp_proxy") != 0)
941 {
942 URI Proxy = string(getenv("ftp_proxy"));
943 if (Proxy.Access == "http")
944 {
945 // Copy over the environment setting
946 char S[300];
947 snprintf(S,sizeof(S),"http_proxy=%s",getenv("ftp_proxy"));
948 putenv(S);
949
950 // Run the http method
951 string Path = flNotFile(argv[0]) + "/http";
952 execl(Path.c_str(),Path.c_str(),0);
953 cerr << "Unable to invoke " << Path << endl;
954 exit(100);
955 }
956 }
957
958 FtpMethod Mth;
959
960 return Mth.Run();
961 }