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