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