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