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