Do not count sizes of to be configured packages
[ntk/apt.git] / methods / ftp.cc
CommitLineData
30b30ec1
AL
1// -*- mode: cpp; mode: fold -*-
2// Description /*{{{*/
2e90f6e0 3// $Id: ftp.cc,v 1.6 1999/04/11 21:23:10 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)
728 return _error->Error("Data socket connect timed out");
729
730 // Read the data..
731 int Res = read(DataFd,Buffer,sizeof(Buffer));
732 if (Res == 0)
733 break;
734 if (Res < 0)
735 {
736 if (errno == EAGAIN)
737 continue;
738 break;
739 }
10861bb5
AL
740
741 MD5.Add(Buffer,Res);
30b30ec1
AL
742 if (To.Write(Buffer,Res) == false)
743 return false;
744 }
745
746 // All done
747 close(DataFd);
748 DataFd = -1;
749
750 // Read the closing message from the server
751 if (ReadResp(Tag,Msg) == false)
752 return false;
753 if (Tag >= 400)
754 return _error->Error("Data transfer failed, server said '%s'",Msg.c_str());
755 return true;
756}
757 /*}}}*/
758
ce0ae89a
AL
759// FtpMethod::FtpMethod - Constructor /*{{{*/
760// ---------------------------------------------------------------------
761/* */
762FtpMethod::FtpMethod() : pkgAcqMethod("1.0",SendConfig)
30b30ec1 763{
ce0ae89a
AL
764 signal(SIGTERM,SigTerm);
765 signal(SIGINT,SigTerm);
30b30ec1 766
ce0ae89a
AL
767 Server = 0;
768 FailFd = -1;
769}
770 /*}}}*/
771// FtpMethod::SigTerm - Handle a fatal signal /*{{{*/
772// ---------------------------------------------------------------------
773/* This closes and timestamps the open file. This is neccessary to get
774 resume behavoir on user abort */
775void FtpMethod::SigTerm(int)
776{
777 if (FailFd == -1)
778 exit(100);
779 close(FailFd);
780
781 // Timestamp
782 struct utimbuf UBuf;
783 UBuf.actime = FailTime;
784 UBuf.modtime = FailTime;
785 utime(FailFile.c_str(),&UBuf);
786
787 exit(100);
788}
789 /*}}}*/
790// FtpMethod::Configuration - Handle a configuration message /*{{{*/
791// ---------------------------------------------------------------------
792/* We stash the desired pipeline depth */
793bool FtpMethod::Configuration(string Message)
794{
795 if (pkgAcqMethod::Configuration(Message) == false)
796 return false;
797
798 TimeOut = _config->FindI("Acquire::Ftp::Timeout",TimeOut);
799 return true;
800}
801 /*}}}*/
802// FtpMethod::Fetch - Fetch a file /*{{{*/
803// ---------------------------------------------------------------------
10861bb5 804/* Fetch a single file, called by the base class.. */
ce0ae89a
AL
805bool FtpMethod::Fetch(FetchItem *Itm)
806{
807 URI Get = Itm->Uri;
808 const char *File = Get.Path.c_str();
809 FetchResult Res;
810 Res.Filename = Itm->DestFile;
811 Res.IMSHit = false;
812
813 // Connect to the server
814 if (Server == 0 || Server->Comp(Get) == false)
30b30ec1 815 {
ce0ae89a
AL
816 delete Server;
817 Server = new FTPConn(Get);
818 }
819
820 // Could not connect is a transient error..
821 if (Server->Open(this) == false)
822 {
823 Fail(true);
824 return true;
825 }
30b30ec1 826
ce0ae89a 827 // Get the files information
f26f6d38 828 Status("Query");
ce0ae89a
AL
829 unsigned long Size;
830 if (Server->Size(File,Size) == false ||
831 Server->ModTime(File,FailTime) == false)
832 {
833 Fail(true);
834 return true;
835 }
836 Res.Size = Size;
837
838 // See if it is an IMS hit
839 if (Itm->LastModified == FailTime)
840 {
841 Res.Size = 0;
842 Res.IMSHit = true;
843 URIDone(Res);
844 return true;
845 }
846
847 // See if the file exists
848 struct stat Buf;
849 if (stat(Itm->DestFile.c_str(),&Buf) == 0)
850 {
851 if (Size == (unsigned)Buf.st_size && FailTime == Buf.st_mtime)
30b30ec1 852 {
ce0ae89a
AL
853 Res.Size = Buf.st_size;
854 Res.LastModified = Buf.st_mtime;
855 URIDone(Res);
856 return true;
30b30ec1
AL
857 }
858
ce0ae89a 859 // Resume?
10861bb5 860 if (FailTime == Buf.st_mtime && Size > (unsigned)Buf.st_size)
ce0ae89a
AL
861 Res.ResumePoint = Buf.st_size;
862 }
863
864 // Open the file
865 MD5Summation MD5;
866 {
867 FileFd Fd(Itm->DestFile,FileFd::WriteAny);
868 if (_error->PendingError() == true)
869 return false;
870
871 URIStart(Res);
872
873 FailFile = Itm->DestFile;
874 FailFile.c_str(); // Make sure we dont do a malloc in the signal handler
875 FailFd = Fd.Fd();
876
877 bool Missing;
878 if (Server->Get(File,Fd,Res.ResumePoint,MD5,Missing) == false)
30b30ec1 879 {
ce0ae89a
AL
880 // If the file is missing we hard fail otherwise transient fail
881 if (Missing == true)
882 return false;
883 Fail(true);
884 return true;
30b30ec1 885 }
ce0ae89a
AL
886
887 Res.Size = Fd.Size();
30b30ec1
AL
888 }
889
ce0ae89a
AL
890 Res.LastModified = FailTime;
891 Res.MD5Sum = MD5.Result();
892
893 // Timestamp
894 struct utimbuf UBuf;
895 time(&UBuf.actime);
896 UBuf.actime = FailTime;
897 UBuf.modtime = FailTime;
898 utime(Queue->DestFile.c_str(),&UBuf);
899 FailFd = -1;
900
901 URIDone(Res);
902
903 return true;
904}
905 /*}}}*/
906
907int main()
908{
909 FtpMethod Mth;
910
911 return Mth.Run();
30b30ec1 912}