]> sjero.net Git - wget/blob - src/ftp-basic.c
[svn] No longer include INET headers in ftp-basic.c.
[wget] / src / ftp-basic.c
1 /* Basic FTP routines.
2    Copyright (C) 1995, 1996, 1997, 1998, 2000 Free Software Foundation, Inc.
3
4 This file is part of GNU Wget.
5
6 GNU Wget is free software; you can redistribute it and/or modify
7 it under the terms of the GNU General Public License as published by
8 the Free Software Foundation; either version 2 of the License, or
9 (at your option) any later version.
10
11 GNU Wget is distributed in the hope that it will be useful,
12 but WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14 GNU General Public License for more details.
15
16 You should have received a copy of the GNU General Public License
17 along with Wget; if not, write to the Free Software
18 Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
19
20 In addition, as a special exception, the Free Software Foundation
21 gives permission to link the code of its release of Wget with the
22 OpenSSL project's "OpenSSL" library (or with modified versions of it
23 that use the same license as the "OpenSSL" library), and distribute
24 the linked executables.  You must obey the GNU General Public License
25 in all respects for all of the code used other than "OpenSSL".  If you
26 modify this file, you may extend this exception to your version of the
27 file, but you are not obligated to do so.  If you do not wish to do
28 so, delete this exception statement from your version.  */
29
30 #include <config.h>
31
32 #include <assert.h>
33 #include <stdio.h>
34 #include <stdlib.h>
35 #include <errno.h>
36
37 #ifdef HAVE_STRING_H
38 # include <string.h>
39 #else
40 # include <strings.h>
41 #endif
42 #ifdef HAVE_UNISTD_H
43 # include <unistd.h>
44 #endif
45 #include <sys/types.h>
46
47 #include "wget.h"
48 #include "utils.h"
49 #include "rbuf.h"
50 #include "connect.h"
51 #include "host.h"
52 #include "ftp.h"
53
54 char ftp_last_respline[128];
55
56 \f
57 /* Get the response of FTP server and allocate enough room to handle
58    it.  <CR> and <LF> characters are stripped from the line, and the
59    line is 0-terminated.  All the response lines but the last one are
60    skipped.  The last line is determined as described in RFC959.  */
61 uerr_t
62 ftp_response (struct rbuf *rbuf, char **line)
63 {
64   int i;
65   int bufsize = 40;
66
67   *line = (char *)xmalloc (bufsize);
68   do
69     {
70       for (i = 0; 1; i++)
71         {
72           int res;
73           if (i > bufsize - 1)
74             *line = (char *)xrealloc (*line, (bufsize <<= 1));
75           res = RBUF_READCHAR (rbuf, *line + i);
76           /* RES is number of bytes read.  */
77           if (res == 1)
78             {
79               if ((*line)[i] == '\n')
80                 {
81                   (*line)[i] = '\0';
82                   /* Get rid of \r.  */
83                   if (i > 0 && (*line)[i - 1] == '\r')
84                     (*line)[i - 1] = '\0';
85                   break;
86                 }
87             }
88           else
89             return FTPRERR;
90         }
91       if (opt.server_response)
92         logprintf (LOG_ALWAYS, "%s\n", *line);
93       else
94         DEBUGP (("%s\n", *line));
95     }
96   while (!(i >= 3 && ISDIGIT (**line) && ISDIGIT ((*line)[1]) &&
97            ISDIGIT ((*line)[2]) && (*line)[3] == ' '));
98   strncpy (ftp_last_respline, *line, sizeof (ftp_last_respline));
99   ftp_last_respline[sizeof (ftp_last_respline) - 1] = '\0';
100   return FTPOK;
101 }
102
103 /* Returns the malloc-ed FTP request, ending with <CR><LF>, printing
104    it if printing is required.  If VALUE is NULL, just use
105    command<CR><LF>.  */
106 static char *
107 ftp_request (const char *command, const char *value)
108 {
109   char *res = (char *)xmalloc (strlen (command)
110                                + (value ? (1 + strlen (value)) : 0)
111                                + 2 + 1);
112   sprintf (res, "%s%s%s\r\n", command, value ? " " : "", value ? value : "");
113   if (opt.server_response)
114     {
115       /* Hack: don't print out password.  */
116       if (strncmp (res, "PASS", 4) != 0)
117         logprintf (LOG_ALWAYS, "--> %s\n", res);
118       else
119         logputs (LOG_ALWAYS, "--> PASS Turtle Power!\n\n");
120     }
121   else
122     DEBUGP (("\n--> %s\n", res));
123   return res;
124 }
125
126 /* Sends the USER and PASS commands to the server, to control
127    connection socket csock.  */
128 uerr_t
129 ftp_login (struct rbuf *rbuf, const char *acc, const char *pass)
130 {
131   uerr_t err;
132   char *request, *respline;
133   int nwritten;
134
135   /* Get greeting.  */
136   err = ftp_response (rbuf, &respline);
137   if (err != FTPOK)
138     {
139       xfree (respline);
140       return err;
141     }
142   if (*respline != '2')
143     {
144       xfree (respline);
145       return FTPSRVERR;
146     }
147   xfree (respline);
148   /* Send USER username.  */
149   request = ftp_request ("USER", acc);
150   nwritten = xwrite (RBUF_FD (rbuf), request, strlen (request), -1);
151   if (nwritten < 0)
152     {
153       xfree (request);
154       return WRITEFAILED;
155     }
156   xfree (request);
157   /* Get appropriate response.  */
158   err = ftp_response (rbuf, &respline);
159   if (err != FTPOK)
160     {
161       xfree (respline);
162       return err;
163     }
164   /* An unprobable possibility of logging without a password.  */
165   if (*respline == '2')
166     {
167       xfree (respline);
168       return FTPOK;
169     }
170   /* Else, only response 3 is appropriate.  */
171   if (*respline != '3')
172     {
173       xfree (respline);
174       return FTPLOGREFUSED;
175     }
176 #ifdef USE_OPIE
177   {
178     static const char *skey_head[] = {
179       "331 s/key ",
180       "331 opiekey "
181     };
182     int i;
183     const char *seed = NULL;
184
185     for (i = 0; i < countof (skey_head); i++)
186       {
187         int l = strlen (skey_head[i]);
188         if (0 == strncasecmp (skey_head[i], respline, l))
189           {
190             seed = respline + l;
191             break;
192           }
193       }
194     if (seed)
195       {
196         int skey_sequence = 0;
197
198         /* Extract the sequence from SEED.  */
199         for (; ISDIGIT (*seed); seed++)
200           skey_sequence = 10 * skey_sequence + *seed - '0';
201         if (*seed == ' ')
202           ++seed;
203         else
204           {
205             xfree (respline);
206             return FTPLOGREFUSED;
207           }
208         /* Replace the password with the SKEY response to the
209            challenge.  */
210         pass = skey_response (skey_sequence, seed, pass);
211       }
212   }
213 #endif /* USE_OPIE */
214   xfree (respline);
215   /* Send PASS password.  */
216   request = ftp_request ("PASS", pass);
217   nwritten = xwrite (RBUF_FD (rbuf), request, strlen (request), -1);
218   if (nwritten < 0)
219     {
220       xfree (request);
221       return WRITEFAILED;
222     }
223   xfree (request);
224   /* Get appropriate response.  */
225   err = ftp_response (rbuf, &respline);
226   if (err != FTPOK)
227     {
228       xfree (respline);
229       return err;
230     }
231   if (*respline != '2')
232     {
233       xfree (respline);
234       return FTPLOGINC;
235     }
236   xfree (respline);
237   /* All OK.  */
238   return FTPOK;
239 }
240
241 static void
242 ip_address_to_port_repr (const ip_address *addr, int port, char *buf, 
243                          size_t buflen)
244 {
245   unsigned char *ptr;
246
247   assert (addr != NULL);
248   assert (addr->type == IPV4_ADDRESS);
249   assert (buf != NULL);
250   /* buf must contain the argument of PORT (of the form a,b,c,d,e,f). */
251   assert (buflen >= 6 * 4);
252
253   ptr = ADDRESS_IPV4_DATA (addr);
254   snprintf (buf, buflen, "%d,%d,%d,%d,%d,%d", ptr[0], ptr[1],
255             ptr[2], ptr[3], (port & 0xff00) >> 8, port & 0xff);
256   buf[buflen - 1] = '\0';
257 }
258
259 /* Bind a port and send the appropriate PORT command to the FTP
260    server.  Use acceptport after RETR, to get the socket of data
261    connection.  */
262 uerr_t
263 ftp_port (struct rbuf *rbuf, int *local_sock)
264 {
265   uerr_t err;
266   char *request, *respline;
267   ip_address addr;
268   int nwritten;
269   int port;
270   /* Must contain the argument of PORT (of the form a,b,c,d,e,f). */
271   char bytes[6 * 4 + 1];
272
273   assert (rbuf != NULL);
274   assert (rbuf_initialized_p (rbuf));
275
276   /* Get the address of this side of the connection. */
277   if (!socket_ip_address (RBUF_FD (rbuf), &addr, ENDPOINT_LOCAL))
278     return FTPSYSERR;
279
280   assert (addr.type == IPV4_ADDRESS);
281
282   /* Setting port to 0 lets the system choose a free port.  */
283   port = 0;
284
285   /* Bind the port.  */
286   *local_sock = bind_local (&addr, &port);
287   if (*local_sock < 0)
288     return FTPSYSERR;
289
290   /* Construct the argument of PORT (of the form a,b,c,d,e,f). */
291   ip_address_to_port_repr (&addr, port, bytes, sizeof (bytes));
292
293   /* Send PORT request.  */
294   request = ftp_request ("PORT", bytes);
295   nwritten = xwrite (RBUF_FD (rbuf), request, strlen (request), -1);
296   if (nwritten < 0)
297     {
298       xfree (request);
299       xclose (*local_sock);
300       return WRITEFAILED;
301     }
302   xfree (request);
303
304   /* Get appropriate response.  */
305   err = ftp_response (rbuf, &respline);
306   if (err != FTPOK)
307     {
308       xfree (respline);
309       xclose (*local_sock);
310       return err;
311     }
312   if (*respline != '2')
313     {
314       xfree (respline);
315       xclose (*local_sock);
316       return FTPPORTERR;
317     }
318   xfree (respline);
319   return FTPOK;
320 }
321
322 #ifdef ENABLE_IPV6
323 static void
324 ip_address_to_lprt_repr (const ip_address *addr, int port, char *buf, 
325                          size_t buflen)
326 {
327   unsigned char *ptr;
328
329   assert (addr != NULL);
330   assert (addr->type == IPV4_ADDRESS || addr->type == IPV6_ADDRESS);
331   assert (buf != NULL);
332   /* buf must contain the argument of LPRT (of the form af,n,h1,h2,...,hn,p1,p2). */
333   assert (buflen >= 21 * 4);
334
335   /* Construct the argument of LPRT (of the form af,n,h1,h2,...,hn,p1,p2). */
336   switch (addr->type) 
337     {
338       case IPV4_ADDRESS: 
339         ptr = ADDRESS_IPV4_DATA (addr);
340         snprintf (buf, buflen, "%d,%d,%d,%d,%d,%d,%d,%d,%d", 4, 4, 
341                   ptr[0], ptr[1], ptr[2], ptr[3], 2,
342                   (port & 0xff00) >> 8, port & 0xff);
343         buf[buflen - 1] = '\0';
344         break;
345       case IPV6_ADDRESS: 
346         ptr = ADDRESS_IPV6_DATA (addr);
347         snprintf (buf, buflen, "%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d",
348                   6, 16, ptr[0], ptr[1], ptr[2], ptr[3], ptr[4], ptr[5], ptr[6], ptr[7], 
349                   ptr[8], ptr[9], ptr[10], ptr[11], ptr[12], ptr[13], ptr[14], ptr[15], 2,
350                   (port & 0xff00) >> 8, port & 0xff);
351         buf[buflen - 1] = '\0';
352         break;
353     }
354 }
355
356 /* Bind a port and send the appropriate PORT command to the FTP
357    server.  Use acceptport after RETR, to get the socket of data
358    connection.  */
359 uerr_t
360 ftp_lprt (struct rbuf *rbuf, int *local_sock)
361 {
362   uerr_t err;
363   char *request, *respline;
364   ip_address addr;
365   int nwritten;
366   int port;
367   /* Must contain the argument of LPRT (of the form af,n,h1,h2,...,hn,p1,p2). */
368   char bytes[21 * 4 + 1];
369
370   assert (rbuf != NULL);
371   assert (rbuf_initialized_p (rbuf));
372
373   /* Get the address of this side of the connection. */
374   if (!socket_ip_address (RBUF_FD (rbuf), &addr, ENDPOINT_LOCAL))
375     return FTPSYSERR;
376
377   assert (addr.type == IPV4_ADDRESS || addr.type == IPV6_ADDRESS);
378
379   /* Setting port to 0 lets the system choose a free port.  */
380   port = 0;
381
382   /* Bind the port.  */
383   *local_sock = bind_local (&addr, &port);
384   if (*local_sock < 0)
385     return FTPSYSERR;
386
387   /* Construct the argument of LPRT (of the form af,n,h1,h2,...,hn,p1,p2). */
388   ip_address_to_lprt_repr (&addr, port, bytes, sizeof (bytes));
389
390   /* Send PORT request.  */
391   request = ftp_request ("LPRT", bytes);
392   nwritten = xwrite (RBUF_FD (rbuf), request, strlen (request), -1);
393   if (nwritten < 0)
394     {
395       xfree (request);
396       xclose (*local_sock);
397       return WRITEFAILED;
398     }
399   xfree (request);
400   /* Get appropriate response.  */
401   err = ftp_response (rbuf, &respline);
402   if (err != FTPOK)
403     {
404       xfree (respline);
405       xclose (*local_sock);
406       return err;
407     }
408   if (*respline != '2')
409     {
410       xfree (respline);
411       xclose (*local_sock);
412       return FTPPORTERR;
413     }
414   xfree (respline);
415   return FTPOK;
416 }
417
418 static void
419 ip_address_to_eprt_repr (const ip_address *addr, int port, char *buf, 
420                          size_t buflen)
421 {
422   int afnum;
423
424   assert (addr != NULL);
425   assert (addr->type == IPV4_ADDRESS || addr->type == IPV6_ADDRESS);
426   assert (buf != NULL);
427   /* buf must contain the argument of EPRT (of the form |af|addr|port|). 
428    * 4 chars for the | separators, INET6_ADDRSTRLEN chars for addr  
429    * 1 char for af (1-2) and 5 chars for port (0-65535) */
430   assert (buflen >= 4 + INET6_ADDRSTRLEN + 1 + 5); 
431
432   /* Construct the argument of EPRT (of the form |af|addr|port|). */
433   afnum = (addr->type == IPV4_ADDRESS ? 1 : 2);
434   snprintf (buf, buflen, "|%d|%s|%d|", afnum, pretty_print_address (addr), port);
435   buf[buflen - 1] = '\0';
436 }
437
438 /* Bind a port and send the appropriate PORT command to the FTP
439    server.  Use acceptport after RETR, to get the socket of data
440    connection.  */
441 uerr_t
442 ftp_eprt (struct rbuf *rbuf, int *local_sock)
443 {
444   uerr_t err;
445   char *request, *respline;
446   ip_address addr;
447   int nwritten;
448   int port;
449   /* Must contain the argument of EPRT (of the form |af|addr|port|). 
450    * 4 chars for the | separators, ENABLE_IPV6_ADDRSTRLEN chars for addr  
451    * 1 char for af (1-2) and 5 chars for port (0-65535) */
452   char bytes[4 + INET6_ADDRSTRLEN + 1 + 5 + 1];
453
454   assert (rbuf != NULL);
455   assert (rbuf_initialized_p(rbuf));
456
457   /* Get the address of this side of the connection. */
458   if (!socket_ip_address (RBUF_FD (rbuf), &addr, ENDPOINT_LOCAL))
459     return FTPSYSERR;
460
461   assert (addr.type == IPV4_ADDRESS || addr.type == IPV6_ADDRESS);
462
463   /* Setting port to 0 lets the system choose a free port.  */
464   port = 0;
465
466   /* Bind the port.  */
467   *local_sock = bind_local (&addr, &port);
468   if (*local_sock < 0)
469     return FTPSYSERR;
470
471   /* Construct the argument of LPRT (of the form af,n,h1,h2,...,hn,p1,p2). */
472   ip_address_to_eprt_repr (&addr, port, bytes, sizeof (bytes));
473
474   /* Send PORT request.  */
475   request = ftp_request ("EPRT", bytes);
476   nwritten = xwrite (RBUF_FD (rbuf), request, strlen (request), -1);
477   if (nwritten < 0)
478     {
479       xfree (request);
480       xclose (*local_sock);
481       return WRITEFAILED;
482     }
483   xfree (request);
484   /* Get appropriate response.  */
485   err = ftp_response (rbuf, &respline);
486   if (err != FTPOK)
487     {
488       xfree (respline);
489       xclose (*local_sock);
490       return err;
491     }
492   if (*respline != '2')
493     {
494       xfree (respline);
495       xclose (*local_sock);
496       return FTPPORTERR;
497     }
498   xfree (respline);
499   return FTPOK;
500 }
501 #endif
502
503 /* Similar to ftp_port, but uses `PASV' to initiate the passive FTP
504    transfer.  Reads the response from server and parses it.  Reads the
505    host and port addresses and returns them.  */
506 uerr_t
507 ftp_pasv (struct rbuf *rbuf, ip_address *addr, int *port)
508 {
509   char *request, *respline, *s;
510   int nwritten, i;
511   uerr_t err;
512   unsigned char tmp[6];
513
514   assert (rbuf != NULL);
515   assert (rbuf_initialized_p (rbuf));
516   assert (addr != NULL);
517   assert (port != NULL);
518
519   memset (addr, 0, sizeof (ip_address));
520
521   /* Form the request.  */
522   request = ftp_request ("PASV", NULL);
523   /* And send it.  */
524   nwritten = xwrite (RBUF_FD (rbuf), request, strlen (request), -1);
525   if (nwritten < 0)
526     {
527       xfree (request);
528       return WRITEFAILED;
529     }
530   xfree (request);
531   /* Get the server response.  */
532   err = ftp_response (rbuf, &respline);
533   if (err != FTPOK)
534     {
535       xfree (respline);
536       return err;
537     }
538   if (*respline != '2')
539     {
540       xfree (respline);
541       return FTPNOPASV;
542     }
543   /* Parse the request.  */
544   s = respline;
545   for (s += 4; *s && !ISDIGIT (*s); s++);
546   if (!*s)
547     return FTPINVPASV;
548   for (i = 0; i < 6; i++)
549     {
550       tmp[i] = 0;
551       for (; ISDIGIT (*s); s++)
552         tmp[i] = (*s - '0') + 10 * tmp[i];
553       if (*s == ',')
554         s++;
555       else if (i < 5)
556         {
557           /* When on the last number, anything can be a terminator.  */
558           xfree (respline);
559           return FTPINVPASV;
560         }
561     }
562   xfree (respline);
563
564   addr->type = IPV4_ADDRESS;
565   memcpy (ADDRESS_IPV4_DATA (addr), tmp, 4);
566   *port = ((tmp[4] << 8) & 0xff00) + tmp[5];
567
568   return FTPOK;
569 }
570
571 #ifdef ENABLE_IPV6
572 /* Similar to ftp_lprt, but uses `LPSV' to initiate the passive FTP
573    transfer.  Reads the response from server and parses it.  Reads the
574    host and port addresses and returns them.  */
575 uerr_t
576 ftp_lpsv (struct rbuf *rbuf, ip_address *addr, int *port)
577 {
578   char *request, *respline, *s;
579   int nwritten, i, af, addrlen, portlen;
580   uerr_t err;
581   unsigned char tmp[16];
582   unsigned char tmpprt[2];
583
584   assert (rbuf != NULL);
585   assert (rbuf_initialized_p(rbuf));
586   assert (addr != NULL);
587   assert (port != NULL);
588
589   memset (addr, 0, sizeof (ip_address));
590
591   /* Form the request.  */
592   request = ftp_request ("LPSV", NULL);
593
594   /* And send it.  */
595   nwritten = xwrite (RBUF_FD (rbuf), request, strlen (request), -1);
596   if (nwritten < 0)
597     {
598       xfree (request);
599       return WRITEFAILED;
600     }
601   xfree (request);
602
603   /* Get the server response.  */
604   err = ftp_response (rbuf, &respline);
605   if (err != FTPOK)
606     {
607       xfree (respline);
608       return err;
609     }
610   if (*respline != '2')
611     {
612       xfree (respline);
613       return FTPNOPASV;
614     }  
615
616   /* Parse the response.  */
617   s = respline;
618   for (s += 4; *s && !ISDIGIT (*s); s++);
619   if (!*s)
620     return FTPINVPASV;
621
622   /* First, get the address family */
623   af = 0;
624   for (; ISDIGIT (*s); s++)
625     af = (*s - '0') + 10 * af;
626
627   if (af != 4 && af != 6)
628     {
629       xfree (respline);
630       return FTPINVPASV;
631     }
632
633   if (!*s || *s++ != ',')
634     {
635       xfree (respline);
636       return FTPINVPASV;
637     }
638
639   /* Then, get the address length */
640   addrlen = 0;
641   for (; ISDIGIT (*s); s++)
642     addrlen = (*s - '0') + 10 * addrlen;
643
644   if (!*s || *s++ != ',')
645     {
646       xfree (respline);
647       return FTPINVPASV;
648     }
649
650   if (addrlen > 16)
651     {
652       xfree (respline);
653       return FTPINVPASV;
654     }
655
656   if ((af == 4 && addrlen != 4)
657       || (af == 6 && addrlen != 16))
658     {
659       xfree (respline);
660       return FTPINVPASV;
661     }
662
663   /* Now, we get the actual address */
664   for (i = 0; i < addrlen; i++)
665     {
666       tmp[i] = 0;
667       for (; ISDIGIT (*s); s++)
668         tmp[i] = (*s - '0') + 10 * tmp[i];
669       if (*s == ',')
670         s++;
671       else
672         {
673           xfree (respline);
674           return FTPINVPASV;
675         }
676     }
677
678   /* Now, get the port length */
679   portlen = 0;
680   for (; ISDIGIT (*s); s++)
681     portlen = (*s - '0') + 10 * portlen;
682
683   if (!*s || *s++ != ',')
684     {
685       xfree (respline);
686       return FTPINVPASV;
687     }
688
689   if (portlen > 2)
690     {
691       xfree (respline);
692       return FTPINVPASV;
693     }
694
695   /* Finally, we get the port number */
696   tmpprt[0] = 0;
697   for (; ISDIGIT (*s); s++)
698     tmpprt[0] = (*s - '0') + 10 * tmpprt[0];
699
700   if (!*s || *s++ != ',')
701     {
702       xfree (respline);
703       return FTPINVPASV;
704     }
705
706   tmpprt[1] = 0;
707   for (; ISDIGIT (*s); s++)
708     tmpprt[1] = (*s - '0') + 10 * tmpprt[1];
709
710   assert (s != NULL);
711
712   if (af == 4)
713     {
714       addr->type = IPV4_ADDRESS;
715       memcpy (ADDRESS_IPV4_DATA (addr), tmp, 4);
716       *port = ((tmpprt[0] << 8) & 0xff00) + tmpprt[1];
717       DEBUGP (("lpsv addr is: %s\n", pretty_print_address(addr)));
718       DEBUGP (("tmpprt[0] is: %d\n", tmpprt[0]));
719       DEBUGP (("tmpprt[1] is: %d\n", tmpprt[1]));
720       DEBUGP (("*port is: %d\n", *port));
721     }
722   else
723     {
724       assert (af == 6);
725       addr->type = IPV6_ADDRESS;
726       memcpy (ADDRESS_IPV6_DATA (addr), tmp, 16);
727       *port = ((tmpprt[0] << 8) & 0xff00) + tmpprt[1];
728       DEBUGP (("lpsv addr is: %s\n", pretty_print_address(addr)));
729       DEBUGP (("tmpprt[0] is: %d\n", tmpprt[0]));
730       DEBUGP (("tmpprt[1] is: %d\n", tmpprt[1]));
731       DEBUGP (("*port is: %d\n", *port));
732     }
733
734   xfree (respline);
735   return FTPOK;
736 }
737
738 /* Similar to ftp_eprt, but uses `EPSV' to initiate the passive FTP
739    transfer.  Reads the response from server and parses it.  Reads the
740    host and port addresses and returns them.  */
741 uerr_t
742 ftp_epsv (struct rbuf *rbuf, ip_address *ip, int *port)
743 {
744   char *request, *respline, *start, delim, *s;
745   int nwritten, i;
746   uerr_t err;
747   int tport;
748
749   assert (rbuf != NULL);
750   assert (rbuf_initialized_p(rbuf));
751   assert (ip != NULL);
752   assert (port != NULL);
753
754   /* IP already contains the IP address of the control connection's
755      peer, so we don't need to call socket_ip_address here.  */
756
757   /* Form the request.  */
758   /* EPSV 1 means that we ask for IPv4 and EPSV 2 means that we ask for IPv6. */
759   request = ftp_request ("EPSV", (ip->type == IPV4_ADDRESS ? "1" : "2"));
760
761   /* And send it.  */
762   nwritten = xwrite (RBUF_FD (rbuf), request, strlen (request), -1);
763   if (nwritten < 0)
764     {
765       xfree (request);
766       return WRITEFAILED;
767     }
768   xfree (request);
769
770   /* Get the server response.  */
771   err = ftp_response (rbuf, &respline);
772   if (err != FTPOK)
773     {
774       xfree (respline);
775       return err;
776     }
777   if (*respline != '2')
778     {
779       xfree (respline);
780       return FTPNOPASV;
781     }  
782
783   assert (respline != NULL);
784
785   DEBUGP(("respline is %s\n", respline));
786
787   /* Parse the response.  */
788   s = respline;
789
790   /* Skip the useless stuff and get what's inside the parentheses */
791   start = strchr (respline, '(');
792   if (start == NULL)
793     {
794       xfree (respline);
795       return FTPINVPASV;
796     }  
797
798   /* Skip the first two void fields */
799   s = start + 1;
800   delim = *s++;
801   if (delim < 33 || delim > 126)
802     {
803       xfree (respline);
804       return FTPINVPASV;
805     }  
806
807   for (i = 0; i < 2; i++)
808     {
809       if (*s++ != delim) 
810         {
811           xfree (respline);
812         return FTPINVPASV;
813         }  
814     }
815
816   /* Finally, get the port number */
817   tport = 0; 
818   for (i = 1; ISDIGIT (*s); s++) 
819     {
820       if (i > 5)
821         {
822           xfree (respline);
823           return FTPINVPASV;
824         }  
825       tport = (*s - '0') + 10 * tport;
826     }
827
828   /* Make sure that the response terminates correcty */
829   if (*s++ != delim)
830     {
831       xfree (respline);
832       return FTPINVPASV;
833     }  
834
835   if (*s++ != ')')
836     {
837       xfree (respline);
838       return FTPINVPASV;
839     }  
840
841   *port = tport;
842
843   xfree (respline);
844   return FTPOK;
845 }
846 #endif
847
848 /* Sends the TYPE request to the server.  */
849 uerr_t
850 ftp_type (struct rbuf *rbuf, int type)
851 {
852   char *request, *respline;
853   int nwritten;
854   uerr_t err;
855   char stype[2];
856
857   /* Construct argument.  */
858   stype[0] = type;
859   stype[1] = 0;
860   /* Send TYPE request.  */
861   request = ftp_request ("TYPE", stype);
862   nwritten = xwrite (RBUF_FD (rbuf), request, strlen (request), -1);
863   if (nwritten < 0)
864     {
865       xfree (request);
866       return WRITEFAILED;
867     }
868   xfree (request);
869   /* Get appropriate response.  */
870   err = ftp_response (rbuf, &respline);
871   if (err != FTPOK)
872     {
873       xfree (respline);
874       return err;
875     }
876   if (*respline != '2')
877     {
878       xfree (respline);
879       return FTPUNKNOWNTYPE;
880     }
881   xfree (respline);
882   /* All OK.  */
883   return FTPOK;
884 }
885
886 /* Changes the working directory by issuing a CWD command to the
887    server.  */
888 uerr_t
889 ftp_cwd (struct rbuf *rbuf, const char *dir)
890 {
891   char *request, *respline;
892   int nwritten;
893   uerr_t err;
894
895   /* Send CWD request.  */
896   request = ftp_request ("CWD", dir);
897   nwritten = xwrite (RBUF_FD (rbuf), request, strlen (request), -1);
898   if (nwritten < 0)
899     {
900       xfree (request);
901       return WRITEFAILED;
902     }
903   xfree (request);
904   /* Get appropriate response.  */
905   err = ftp_response (rbuf, &respline);
906   if (err != FTPOK)
907     {
908       xfree (respline);
909       return err;
910     }
911   if (*respline == '5')
912     {
913       xfree (respline);
914       return FTPNSFOD;
915     }
916   if (*respline != '2')
917     {
918       xfree (respline);
919       return FTPRERR;
920     }
921   xfree (respline);
922   /* All OK.  */
923   return FTPOK;
924 }
925
926 /* Sends REST command to the FTP server.  */
927 uerr_t
928 ftp_rest (struct rbuf *rbuf, long offset)
929 {
930   char *request, *respline;
931   int nwritten;
932   uerr_t err;
933   static char numbuf[24]; /* Buffer for the number */
934
935   number_to_string (numbuf, offset);
936   request = ftp_request ("REST", numbuf);
937   nwritten = xwrite (RBUF_FD (rbuf), request, strlen (request), -1);
938   if (nwritten < 0)
939     {
940       xfree (request);
941       return WRITEFAILED;
942     }
943   xfree (request);
944   /* Get appropriate response.  */
945   err = ftp_response (rbuf, &respline);
946   if (err != FTPOK)
947     {
948       xfree (respline);
949       return err;
950     }
951   if (*respline != '3')
952     {
953       xfree (respline);
954       return FTPRESTFAIL;
955     }
956   xfree (respline);
957   /* All OK.  */
958   return FTPOK;
959 }
960
961 /* Sends RETR command to the FTP server.  */
962 uerr_t
963 ftp_retr (struct rbuf *rbuf, const char *file)
964 {
965   char *request, *respline;
966   int nwritten;
967   uerr_t err;
968
969   /* Send RETR request.  */
970   request = ftp_request ("RETR", file);
971   nwritten = xwrite (RBUF_FD (rbuf), request, strlen (request), -1);
972   if (nwritten < 0)
973     {
974       xfree (request);
975       return WRITEFAILED;
976     }
977   xfree (request);
978   /* Get appropriate response.  */
979   err = ftp_response (rbuf, &respline);
980   if (err != FTPOK)
981     {
982       xfree (respline);
983       return err;
984     }
985   if (*respline == '5')
986     {
987       xfree (respline);
988       return FTPNSFOD;
989     }
990   if (*respline != '1')
991     {
992       xfree (respline);
993       return FTPRERR;
994     }
995   xfree (respline);
996   /* All OK.  */
997   return FTPOK;
998 }
999
1000 /* Sends the LIST command to the server.  If FILE is NULL, send just
1001    `LIST' (no space).  */
1002 uerr_t
1003 ftp_list (struct rbuf *rbuf, const char *file)
1004 {
1005   char *request, *respline;
1006   int nwritten;
1007   uerr_t err;
1008
1009   /* Send LIST request.  */
1010   request = ftp_request ("LIST", file);
1011   nwritten = xwrite (RBUF_FD (rbuf), request, strlen (request), -1);
1012   if (nwritten < 0)
1013     {
1014       xfree (request);
1015       return WRITEFAILED;
1016     }
1017   xfree (request);
1018   /* Get appropriate respone.  */
1019   err = ftp_response (rbuf, &respline);
1020   if (err != FTPOK)
1021     {
1022       xfree (respline);
1023       return err;
1024     }
1025   if (*respline == '5')
1026     {
1027       xfree (respline);
1028       return FTPNSFOD;
1029     }
1030   if (*respline != '1')
1031     {
1032       xfree (respline);
1033       return FTPRERR;
1034     }
1035   xfree (respline);
1036   /* All OK.  */
1037   return FTPOK;
1038 }
1039
1040 /* Sends the SYST command to the server. */
1041 uerr_t
1042 ftp_syst (struct rbuf *rbuf, enum stype *server_type)
1043 {
1044   char *request, *respline;
1045   int nwritten;
1046   uerr_t err;
1047
1048   /* Send SYST request.  */
1049   request = ftp_request ("SYST", NULL);
1050   nwritten = xwrite (RBUF_FD (rbuf), request, strlen (request), -1);
1051   if (nwritten < 0)
1052     {
1053       xfree (request);
1054       return WRITEFAILED;
1055     }
1056   xfree (request);
1057
1058   /* Get appropriate response.  */
1059   err = ftp_response (rbuf, &respline);
1060   if (err != FTPOK)
1061     {
1062       xfree (respline);
1063       return err;
1064     }
1065   if (*respline == '5')
1066     {
1067       xfree (respline);
1068       return FTPSRVERR;
1069     }
1070
1071   /* Skip the number (215, but 200 (!!!) in case of VMS) */
1072   strtok (respline, " ");
1073
1074   /* Which system type has been reported (we are interested just in the
1075      first word of the server response)?  */
1076   request = strtok (NULL, " ");
1077
1078   if (!strcasecmp (request, "VMS"))
1079     *server_type = ST_VMS;
1080   else if (!strcasecmp (request, "UNIX"))
1081     *server_type = ST_UNIX;
1082   else if (!strcasecmp (request, "WINDOWS_NT")
1083            || !strcasecmp (request, "WINDOWS2000"))
1084     *server_type = ST_WINNT;
1085   else if (!strcasecmp (request, "MACOS"))
1086     *server_type = ST_MACOS;
1087   else if (!strcasecmp (request, "OS/400"))
1088     *server_type = ST_OS400;
1089   else
1090     *server_type = ST_OTHER;
1091
1092   xfree (respline);
1093   /* All OK.  */
1094   return FTPOK;
1095 }
1096
1097 /* Sends the PWD command to the server. */
1098 uerr_t
1099 ftp_pwd (struct rbuf *rbuf, char **pwd)
1100 {
1101   char *request, *respline;
1102   int nwritten;
1103   uerr_t err;
1104
1105   /* Send PWD request.  */
1106   request = ftp_request ("PWD", NULL);
1107   nwritten = xwrite (RBUF_FD (rbuf), request, strlen (request), -1);
1108   if (nwritten < 0)
1109     {
1110       xfree (request);
1111       return WRITEFAILED;
1112     }
1113   xfree (request);
1114   /* Get appropriate response.  */
1115   err = ftp_response (rbuf, &respline);
1116   if (err != FTPOK)
1117     {
1118       xfree (respline);
1119       return err;
1120     }
1121   if (*respline == '5')
1122     {
1123       xfree (respline);
1124       return FTPSRVERR;
1125     }
1126
1127   /* Skip the number (257), leading citation mark, trailing citation mark
1128      and everything following it. */
1129   strtok (respline, "\"");
1130   request = strtok (NULL, "\"");
1131
1132   /* Has the `pwd' been already allocated?  Free! */
1133   xfree_null (*pwd);
1134
1135   *pwd = xstrdup (request);
1136
1137   xfree (respline);
1138   /* All OK.  */
1139   return FTPOK;
1140 }
1141
1142 /* Sends the SIZE command to the server, and returns the value in 'size'.
1143  * If an error occurs, size is set to zero. */
1144 uerr_t
1145 ftp_size (struct rbuf *rbuf, const char *file, long int *size)
1146 {
1147   char *request, *respline;
1148   int nwritten;
1149   uerr_t err;
1150
1151   /* Send PWD request.  */
1152   request = ftp_request ("SIZE", file);
1153   nwritten = xwrite (RBUF_FD (rbuf), request, strlen (request), -1);
1154   if (nwritten < 0)
1155     {
1156       xfree (request);
1157       *size = 0;
1158       return WRITEFAILED;
1159     }
1160   xfree (request);
1161   /* Get appropriate response.  */
1162   err = ftp_response (rbuf, &respline);
1163   if (err != FTPOK)
1164     {
1165       xfree (respline);
1166       *size = 0;
1167       return err;
1168     }
1169   if (*respline == '5')
1170     {
1171       /* 
1172        * Probably means SIZE isn't supported on this server.
1173        * Error is nonfatal since SIZE isn't in RFC 959 
1174        */
1175       xfree (respline);
1176       *size = 0;
1177       return FTPOK;
1178     }
1179
1180   errno = 0;
1181   *size = strtol (respline + 4, NULL, 0);
1182   if (errno) 
1183     {
1184       /* 
1185        * Couldn't parse the response for some reason.  On the (few)
1186        * tests I've done, the response is 213 <SIZE> with nothing else -
1187        * maybe something a bit more resilient is necessary.  It's not a
1188        * fatal error, however.
1189        */
1190       xfree (respline);
1191       *size = 0;
1192       return FTPOK;
1193     }
1194
1195   xfree (respline);
1196   /* All OK.  */
1197   return FTPOK;
1198 }
1199
1200 /* If URL's params are of the form "type=X", return character X.
1201    Otherwise, return 'I' (the default type).  */
1202 char
1203 ftp_process_type (const char *params)
1204 {
1205   if (params
1206       && 0 == strncasecmp (params, "type=", 5)
1207       && params[5] != '\0')
1208     return TOUPPER (params[5]);
1209   else
1210     return 'I';
1211 }