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