]> git.saurik.com Git - apt.git/blame - methods/ftp.cc
Fixed size mismatch
[apt.git] / methods / ftp.cc
CommitLineData
30b30ec1
AL
1// -*- mode: cpp; mode: fold -*-
2// Description /*{{{*/
f26f6d38 3// $Id: ftp.cc,v 1.5 1999/03/15 19:51:27 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{
289 // Suck in a line
290 while (Len < sizeof(Buffer))
291 {
292 // Scan the buffer for a new line
293 for (unsigned int I = 0; I != Len; I++)
294 {
295 // Escape some special chars
296 if (Buffer[I] == 0)
297 Buffer[I] = '?';
298
299 // End of line?
300 if (Buffer[I] != '\n')
301 continue;
302
303 I++;
304 Text = string(Buffer,I);
305 memmove(Buffer,Buffer+I,Len - I);
306 Len -= I;
307 return true;
308 }
309
310 // Wait for some data..
311 if (WaitFd(ServerFd,false,TimeOut) == false)
ce0ae89a
AL
312 {
313 Close();
30b30ec1 314 return _error->Error("Connection timeout");
ce0ae89a 315 }
30b30ec1
AL
316
317 // Suck it back
318 int Res = read(ServerFd,Buffer,sizeof(Buffer) - Len);
319 if (Res <= 0)
ce0ae89a
AL
320 {
321 Close();
30b30ec1 322 return _error->Errno("read","Read error");
ce0ae89a 323 }
30b30ec1
AL
324 Len += Res;
325 }
326
327 return _error->Error("A response overflowed the buffer.");
328}
329 /*}}}*/
330// FTPConn::ReadResp - Read a full response from the server /*{{{*/
331// ---------------------------------------------------------------------
332/* This reads a reply code from the server, it handles both p */
333bool FTPConn::ReadResp(unsigned int &Ret,string &Text)
334{
335 // Grab the first line of the response
336 string Msg;
337 if (ReadLine(Msg) == false)
338 return false;
339
340 // Get the ID code
341 char *End;
342 Ret = strtol(Msg.c_str(),&End,10);
343 if (End - Msg.c_str() != 3)
344 return _error->Error("Protocol corruption");
345
346 // All done ?
347 Text = Msg.c_str()+4;
348 if (*End == ' ')
349 {
350 if (Debug == true)
ce0ae89a 351 cerr << "<- '" << QuoteString(Text,"") << "'" << endl;
30b30ec1
AL
352 return true;
353 }
354
355 if (*End != '-')
356 return _error->Error("Protocol corruption");
357
358 /* Okay, here we do the continued message trick. This is foolish, but
359 proftpd follows the protocol as specified and wu-ftpd doesn't, so
360 we filter. I wonder how many clients break if you use proftpd and
361 put a '- in the 3rd spot in the message? */
362 char Leader[4];
363 strncpy(Leader,Msg.c_str(),3);
364 Leader[3] = 0;
365 while (ReadLine(Msg) == true)
366 {
367 // Short, it must be using RFC continuation..
368 if (Msg.length() < 4)
369 {
370 Text += Msg;
371 continue;
372 }
373
374 // Oops, finished
375 if (strncmp(Msg.c_str(),Leader,3) == 0 && Msg[3] == ' ')
376 {
377 Text += Msg.c_str()+4;
378 break;
379 }
380
381 // This message has the wu-ftpd style reply code prefixed
382 if (strncmp(Msg.c_str(),Leader,3) == 0 && Msg[3] == '-')
383 {
384 Text += Msg.c_str()+4;
385 continue;
386 }
387
388 // Must be RFC style prefixing
389 Text += Msg;
390 }
391
392 if (Debug == true && _error->PendingError() == false)
ce0ae89a 393 cerr << "<- '" << QuoteString(Text,"") << "'" << endl;
30b30ec1
AL
394
395 return !_error->PendingError();
396}
397 /*}}}*/
398// FTPConn::WriteMsg - Send a message to the server /*{{{*/
399// ---------------------------------------------------------------------
400/* Simple printf like function.. */
401bool FTPConn::WriteMsg(unsigned int &Ret,string &Text,const char *Fmt,...)
402{
403 va_list args;
404 va_start(args,Fmt);
405
406 // sprintf the description
407 char S[400];
408 vsnprintf(S,sizeof(S) - 4,Fmt,args);
409 strcat(S,"\r\n");
410
411 if (Debug == true)
ce0ae89a 412 cerr << "-> '" << QuoteString(S,"") << "'" << endl;
30b30ec1
AL
413
414 // Send it off
415 unsigned long Len = strlen(S);
416 unsigned long Start = 0;
417 while (Len != 0)
418 {
419 if (WaitFd(ServerFd,true,TimeOut) == false)
ce0ae89a
AL
420 {
421 Close();
30b30ec1 422 return _error->Error("Connection timeout");
ce0ae89a 423 }
30b30ec1
AL
424
425 int Res = write(ServerFd,S + Start,Len);
426 if (Res <= 0)
ce0ae89a
AL
427 {
428 Close();
30b30ec1 429 return _error->Errno("write","Write Error");
ce0ae89a
AL
430 }
431
30b30ec1
AL
432 Len -= Res;
433 Start += Res;
434 }
435
436 return ReadResp(Ret,Text);
437}
438 /*}}}*/
439// FTPConn::GoPasv - Enter Passive mode /*{{{*/
440// ---------------------------------------------------------------------
441/* Try to enter passive mode, the return code does not indicate if passive
442 mode could or could not be established, only if there was a fatal error.
443 Borrowed mostly from lftp. We have to enter passive mode every time
444 we make a data connection :| */
445bool FTPConn::GoPasv()
446{
447 // Try to enable pasv mode
448 unsigned int Tag;
449 string Msg;
450 if (WriteMsg(Tag,Msg,"PASV") == false)
451 return false;
452
453 // Unsupported function
454 string::size_type Pos = Msg.find('(');
455 if (Tag >= 400 || Pos == string::npos)
456 {
457 memset(&PasvAddr,0,sizeof(PasvAddr));
458 return true;
459 }
460
461 // Scan it
462 unsigned a0,a1,a2,a3,p0,p1;
463 if (sscanf(Msg.c_str() + Pos,"(%u,%u,%u,%u,%u,%u)",&a0,&a1,&a2,&a3,&p0,&p1) != 6)
464 {
465 memset(&PasvAddr,0,sizeof(PasvAddr));
466 return true;
467 }
468
469 // lftp used this horrid byte order manipulation.. Ik.
470 PasvAddr.sin_family = AF_INET;
471 unsigned char *a;
472 unsigned char *p;
473 a = (unsigned char *)&PasvAddr.sin_addr;
474 p = (unsigned char *)&PasvAddr.sin_port;
475
476 // Some evil servers return 0 to mean their addr
477 if (a0 == 0 && a1 == 0 && a2 == 0 && a3 == 0)
478 {
479 PasvAddr.sin_addr = Peer.sin_addr;
480 }
481 else
482 {
483 a[0] = a0;
484 a[1] = a1;
485 a[2] = a2;
486 a[3] = a3;
487 }
488
489 p[0] = p0;
490 p[1] = p1;
491
492 return true;
493}
494 /*}}}*/
495// FTPConn::Size - Return the size of a file /*{{{*/
496// ---------------------------------------------------------------------
497/* Grab the file size from the server, 0 means no size or empty file */
ce0ae89a 498bool FTPConn::Size(const char *Path,unsigned long &Size)
30b30ec1
AL
499{
500 // Query the size
501 unsigned int Tag;
502 string Msg;
ce0ae89a 503 Size = 0;
30b30ec1
AL
504 if (WriteMsg(Tag,Msg,"SIZE %s",Path) == false)
505 return false;
506
507 char *End;
ce0ae89a 508 Size = strtol(Msg.c_str(),&End,10);
30b30ec1 509 if (Tag >= 400 || End == Msg.c_str())
ce0ae89a
AL
510 Size = 0;
511 return true;
30b30ec1
AL
512}
513 /*}}}*/
514// FTPConn::ModTime - Return the modification time of the file /*{{{*/
515// ---------------------------------------------------------------------
516/* Like Size no error is returned if the command is not supported. If the
517 command fails then time is set to the current time of day to fool
518 date checks. */
519bool FTPConn::ModTime(const char *Path, time_t &Time)
520{
521 Time = time(&Time);
522
523 // Query the mod time
524 unsigned int Tag;
525 string Msg;
526 if (WriteMsg(Tag,Msg,"MDTM %s",Path) == false)
527 return false;
528 if (Tag >= 400 || Msg.empty() == true || isdigit(Msg[0]) == 0)
529 return true;
530
531 // Parse it
532 struct tm tm;
533 memset(&tm,0,sizeof(tm));
534 if (sscanf(Msg.c_str(),"%4d%2d%2d%2d%2d%2d",&tm.tm_year,&tm.tm_mon,
535 &tm.tm_mday,&tm.tm_hour,&tm.tm_min,&tm.tm_sec) != 6)
536 return true;
537
538 tm.tm_year -= 1900;
539 tm.tm_mon--;
540
541 /* We use timegm from the GNU C library, libapt-pkg will provide this
542 symbol if it does not exist */
543 Time = timegm(&tm);
544 return true;
545}
546 /*}}}*/
547// FTPConn::CreateDataFd - Get a data connection /*{{{*/
548// ---------------------------------------------------------------------
549/* Create the data connection. Call FinalizeDataFd after this though.. */
550bool FTPConn::CreateDataFd()
551{
552 close(DataFd);
553 DataFd = -1;
554
555 // Attempt to enter passive mode.
556 if (TryPassive == true)
557 {
558 if (GoPasv() == false)
559 return false;
560
561 // Oops, didn't work out, don't bother trying again.
562 if (PasvAddr.sin_port == 0)
563 TryPassive = false;
564 }
565
566 // Passive mode?
567 if (PasvAddr.sin_port != 0)
568 {
569 // Get a socket
570 if ((DataFd = socket(AF_INET,SOCK_STREAM,0)) < 0)
571 return _error->Errno("socket","Could not create a socket");
572
573 // Connect to the server
574 SetNonBlock(DataFd,true);
575 if (connect(DataFd,(sockaddr *)&PasvAddr,sizeof(PasvAddr)) < 0 &&
576 errno != EINPROGRESS)
577 return _error->Errno("socket","Could not create a socket");
578
579 /* This implements a timeout for connect by opening the connection
580 nonblocking */
581 if (WaitFd(ServerFd,true,TimeOut) == false)
582 return _error->Error("Could not connect data socket, connection timed out");
583 unsigned int Err;
584 unsigned int Len = sizeof(Err);
585 if (getsockopt(ServerFd,SOL_SOCKET,SO_ERROR,&Err,&Len) != 0)
586 return _error->Errno("getsockopt","Failed");
587 if (Err != 0)
588 return _error->Error("Could not connect.");
589
590 return true;
591 }
592
593 // Port mode :<
2de438e7
AL
594 close(DataListenFd);
595 DataListenFd = -1;
596
597 // Get a socket
598 if ((DataListenFd = socket(AF_INET,SOCK_STREAM,0)) < 0)
599 return _error->Errno("socket","Could not create a socket");
30b30ec1 600
2de438e7 601 // Bind and listen
30b30ec1 602 sockaddr_in Addr;
2de438e7
AL
603 memset(&Addr,0,sizeof(Addr));
604 if (bind(DataListenFd,(sockaddr *)&Addr,sizeof(Addr)) < 0)
605 return _error->Errno("bind","Could not bind a socket");
606 if (listen(DataListenFd,1) < 0)
607 return _error->Errno("listen","Could not listen on the socket");
608 SetNonBlock(DataListenFd,true);
609
610 // Determine the name to send to the remote
30b30ec1
AL
611 sockaddr_in Addr2;
612 socklen_t Jnk = sizeof(Addr);
613 if (getsockname(DataListenFd,(sockaddr *)&Addr,&Jnk) < 0)
614 return _error->Errno("getsockname","Could not determine the socket's name");
615 Jnk = sizeof(Addr2);
616 if (getsockname(ServerFd,(sockaddr *)&Addr2,&Jnk) < 0)
617 return _error->Errno("getsockname","Could not determine the socket's name");
618
619 // This bit ripped from qftp
620 unsigned long badr = ntohl(*(unsigned long *)&Addr2.sin_addr);
621 unsigned long bp = ntohs(Addr.sin_port);
622
623 // Send the port command
624 unsigned int Tag;
625 string Msg;
626 if (WriteMsg(Tag,Msg,"PORT %d,%d,%d,%d,%d,%d",
627 (int) (badr >> 24) & 0xff, (int) (badr >> 16) & 0xff,
628 (int) (badr >> 8) & 0xff, (int) badr & 0xff,
629 (int) (bp >> 8) & 0xff, (int) bp & 0xff) == false)
630 return false;
631 if (Tag >= 400)
632 return _error->Error("Unable to send port command");
633
634 return true;
635}
636 /*}}}*/
637// FTPConn::Finalize - Complete the Data connection /*{{{*/
638// ---------------------------------------------------------------------
639/* If the connection is in port mode this waits for the other end to hook
640 up to us. */
641bool FTPConn::Finalize()
642{
643 // Passive mode? Do nothing
644 if (PasvAddr.sin_port != 0)
645 return true;
646
647 // Close any old socket..
648 close(DataFd);
649 DataFd = -1;
650
651 // Wait for someone to connect..
652 if (WaitFd(DataListenFd,false,TimeOut) == false)
653 return _error->Error("Data socket connect timed out");
654
655 // Accept the connection
656 struct sockaddr_in Addr;
657 socklen_t Len = sizeof(Addr);
658 DataFd = accept(DataListenFd,(struct sockaddr *)&Addr,&Len);
659 if (DataFd < 0)
660 return _error->Errno("accept","Unable to accept connection");
661
2de438e7
AL
662 close(DataListenFd);
663 DataListenFd = -1;
664
30b30ec1
AL
665 return true;
666}
667 /*}}}*/
668// FTPConn::Get - Get a file /*{{{*/
669// ---------------------------------------------------------------------
670/* This opens a data connection, sends REST and RETR and then
671 transfers the file over. */
ce0ae89a
AL
672bool FTPConn::Get(const char *Path,FileFd &To,unsigned long Resume,
673 MD5Summation &MD5,bool &Missing)
30b30ec1 674{
ce0ae89a 675 Missing = false;
30b30ec1
AL
676 if (CreateDataFd() == false)
677 return false;
678
679 unsigned int Tag;
ce0ae89a 680 string Msg;
30b30ec1
AL
681 if (Resume != 0)
682 {
683 if (WriteMsg(Tag,Msg,"REST %u",Resume) == false)
684 return false;
685 if (Tag >= 400)
686 Resume = 0;
687 }
688
689 if (To.Truncate(Resume) == false)
690 return false;
10861bb5
AL
691
692 if (To.Seek(0) == false)
693 return false;
694
695 if (Resume != 0)
696 {
697 if (MD5.AddFD(To.Fd(),Resume) == false)
698 {
699 _error->Errno("read","Problem hashing file");
700 return false;
701 }
702 }
30b30ec1
AL
703
704 // Send the get command
705 if (WriteMsg(Tag,Msg,"RETR %s",Path) == false)
706 return false;
707
708 if (Tag >= 400)
ce0ae89a
AL
709 {
710 if (Tag == 550)
711 Missing = true;
30b30ec1 712 return _error->Error("Unable to fetch file, server said '%s'",Msg.c_str());
ce0ae89a 713 }
30b30ec1
AL
714
715 // Finish off the data connection
716 if (Finalize() == false)
717 return false;
718
719 // Copy loop
720 unsigned char Buffer[4096];
721 while (1)
722 {
723 // Wait for some data..
724 if (WaitFd(DataFd,false,TimeOut) == false)
725 return _error->Error("Data socket connect timed out");
726
727 // Read the data..
728 int Res = read(DataFd,Buffer,sizeof(Buffer));
729 if (Res == 0)
730 break;
731 if (Res < 0)
732 {
733 if (errno == EAGAIN)
734 continue;
735 break;
736 }
10861bb5
AL
737
738 MD5.Add(Buffer,Res);
30b30ec1
AL
739 if (To.Write(Buffer,Res) == false)
740 return false;
741 }
742
743 // All done
744 close(DataFd);
745 DataFd = -1;
746
747 // Read the closing message from the server
748 if (ReadResp(Tag,Msg) == false)
749 return false;
750 if (Tag >= 400)
751 return _error->Error("Data transfer failed, server said '%s'",Msg.c_str());
752 return true;
753}
754 /*}}}*/
755
ce0ae89a
AL
756// FtpMethod::FtpMethod - Constructor /*{{{*/
757// ---------------------------------------------------------------------
758/* */
759FtpMethod::FtpMethod() : pkgAcqMethod("1.0",SendConfig)
30b30ec1 760{
ce0ae89a
AL
761 signal(SIGTERM,SigTerm);
762 signal(SIGINT,SigTerm);
30b30ec1 763
ce0ae89a
AL
764 Server = 0;
765 FailFd = -1;
766}
767 /*}}}*/
768// FtpMethod::SigTerm - Handle a fatal signal /*{{{*/
769// ---------------------------------------------------------------------
770/* This closes and timestamps the open file. This is neccessary to get
771 resume behavoir on user abort */
772void FtpMethod::SigTerm(int)
773{
774 if (FailFd == -1)
775 exit(100);
776 close(FailFd);
777
778 // Timestamp
779 struct utimbuf UBuf;
780 UBuf.actime = FailTime;
781 UBuf.modtime = FailTime;
782 utime(FailFile.c_str(),&UBuf);
783
784 exit(100);
785}
786 /*}}}*/
787// FtpMethod::Configuration - Handle a configuration message /*{{{*/
788// ---------------------------------------------------------------------
789/* We stash the desired pipeline depth */
790bool FtpMethod::Configuration(string Message)
791{
792 if (pkgAcqMethod::Configuration(Message) == false)
793 return false;
794
795 TimeOut = _config->FindI("Acquire::Ftp::Timeout",TimeOut);
796 return true;
797}
798 /*}}}*/
799// FtpMethod::Fetch - Fetch a file /*{{{*/
800// ---------------------------------------------------------------------
10861bb5 801/* Fetch a single file, called by the base class.. */
ce0ae89a
AL
802bool FtpMethod::Fetch(FetchItem *Itm)
803{
804 URI Get = Itm->Uri;
805 const char *File = Get.Path.c_str();
806 FetchResult Res;
807 Res.Filename = Itm->DestFile;
808 Res.IMSHit = false;
809
810 // Connect to the server
811 if (Server == 0 || Server->Comp(Get) == false)
30b30ec1 812 {
ce0ae89a
AL
813 delete Server;
814 Server = new FTPConn(Get);
815 }
816
817 // Could not connect is a transient error..
818 if (Server->Open(this) == false)
819 {
820 Fail(true);
821 return true;
822 }
30b30ec1 823
ce0ae89a 824 // Get the files information
f26f6d38 825 Status("Query");
ce0ae89a
AL
826 unsigned long Size;
827 if (Server->Size(File,Size) == false ||
828 Server->ModTime(File,FailTime) == false)
829 {
830 Fail(true);
831 return true;
832 }
833 Res.Size = Size;
834
835 // See if it is an IMS hit
836 if (Itm->LastModified == FailTime)
837 {
838 Res.Size = 0;
839 Res.IMSHit = true;
840 URIDone(Res);
841 return true;
842 }
843
844 // See if the file exists
845 struct stat Buf;
846 if (stat(Itm->DestFile.c_str(),&Buf) == 0)
847 {
848 if (Size == (unsigned)Buf.st_size && FailTime == Buf.st_mtime)
30b30ec1 849 {
ce0ae89a
AL
850 Res.Size = Buf.st_size;
851 Res.LastModified = Buf.st_mtime;
852 URIDone(Res);
853 return true;
30b30ec1
AL
854 }
855
ce0ae89a 856 // Resume?
10861bb5 857 if (FailTime == Buf.st_mtime && Size > (unsigned)Buf.st_size)
ce0ae89a
AL
858 Res.ResumePoint = Buf.st_size;
859 }
860
861 // Open the file
862 MD5Summation MD5;
863 {
864 FileFd Fd(Itm->DestFile,FileFd::WriteAny);
865 if (_error->PendingError() == true)
866 return false;
867
868 URIStart(Res);
869
870 FailFile = Itm->DestFile;
871 FailFile.c_str(); // Make sure we dont do a malloc in the signal handler
872 FailFd = Fd.Fd();
873
874 bool Missing;
875 if (Server->Get(File,Fd,Res.ResumePoint,MD5,Missing) == false)
30b30ec1 876 {
ce0ae89a
AL
877 // If the file is missing we hard fail otherwise transient fail
878 if (Missing == true)
879 return false;
880 Fail(true);
881 return true;
30b30ec1 882 }
ce0ae89a
AL
883
884 Res.Size = Fd.Size();
30b30ec1
AL
885 }
886
ce0ae89a
AL
887 Res.LastModified = FailTime;
888 Res.MD5Sum = MD5.Result();
889
890 // Timestamp
891 struct utimbuf UBuf;
892 time(&UBuf.actime);
893 UBuf.actime = FailTime;
894 UBuf.modtime = FailTime;
895 utime(Queue->DestFile.c_str(),&UBuf);
896 FailFd = -1;
897
898 URIDone(Res);
899
900 return true;
901}
902 /*}}}*/
903
904int main()
905{
906 FtpMethod Mth;
907
908 return Mth.Run();
30b30ec1 909}