]> sjero.net Git - wget/blob - src/ftp-basic.c
[svn] Networking improvements: get rid of the MSOCK global variable,
[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, int 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 = ADDRESS_IPV4_DATA (addr);
269   snprintf (buf, buflen, "%d,%d,%d,%d,%d,%d", ptr[0], ptr[1],
270             ptr[2], ptr[3], (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, int *local_sock)
279 {
280   uerr_t err;
281   char *request, *respline;
282   ip_address addr;
283   int nwritten;
284   int 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, local_sock);
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       CLOSE (*local_sock);
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       CLOSE (*local_sock);
325       return err;
326     }
327   if (*respline != '2')
328     {
329       xfree (respline);
330       CLOSE (*local_sock);
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, int 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 = ADDRESS_IPV4_DATA (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                   (port & 0xff00) >> 8, port & 0xff);
358         buf[buflen - 1] = '\0';
359         break;
360       case IPV6_ADDRESS: 
361         ptr = ADDRESS_IPV6_DATA (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                   (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, int *local_sock)
376 {
377   uerr_t err;
378   char *request, *respline;
379   ip_address addr;
380   int nwritten;
381   int 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, local_sock);
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       CLOSE (*local_sock);
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       CLOSE (*local_sock);
421       return err;
422     }
423   if (*respline != '2')
424     {
425       xfree (respline);
426       CLOSE (*local_sock);
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, int 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, int *local_sock)
458 {
459   uerr_t err;
460   char *request, *respline;
461   ip_address addr;
462   int nwritten;
463   int 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, local_sock);
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       CLOSE (*local_sock);
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       CLOSE (*local_sock);
505       return err;
506     }
507   if (*respline != '2')
508     {
509       xfree (respline);
510       CLOSE (*local_sock);
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, int *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   memcpy (ADDRESS_IPV4_DATA (addr), tmp, 4);
581   *port = ((tmp[4] << 8) & 0xff00) + tmp[5];
582
583   return FTPOK;
584 }
585
586 #ifdef ENABLE_IPV6
587 /* Similar to ftp_lprt, but uses `LPSV' to initiate the passive FTP
588    transfer.  Reads the response from server and parses it.  Reads the
589    host and port addresses and returns them.  */
590 uerr_t
591 ftp_lpsv (struct rbuf *rbuf, ip_address *addr, int *port)
592 {
593   char *request, *respline, *s;
594   int nwritten, i, af, addrlen, portlen;
595   uerr_t err;
596   unsigned char tmp[16];
597   unsigned char tmpprt[2];
598
599   assert (rbuf != NULL);
600   assert (rbuf_initialized_p(rbuf));
601   assert (addr != NULL);
602   assert (port != NULL);
603
604   memset (addr, 0, sizeof (ip_address));
605
606   /* Form the request.  */
607   request = ftp_request ("LPSV", NULL);
608
609   /* And send it.  */
610   nwritten = iwrite (RBUF_FD (rbuf), request, strlen (request));
611   if (nwritten < 0)
612     {
613       xfree (request);
614       return WRITEFAILED;
615     }
616   xfree (request);
617
618   /* Get the server response.  */
619   err = ftp_response (rbuf, &respline);
620   if (err != FTPOK)
621     {
622       xfree (respline);
623       return err;
624     }
625   if (*respline != '2')
626     {
627       xfree (respline);
628       return FTPNOPASV;
629     }  
630
631   /* Parse the response.  */
632   s = respline;
633   for (s += 4; *s && !ISDIGIT (*s); s++);
634   if (!*s)
635     return FTPINVPASV;
636
637   /* First, get the address family */
638   af = 0;
639   for (; ISDIGIT (*s); s++)
640     af = (*s - '0') + 10 * af;
641
642   if (af != 4 && af != 6)
643     {
644       xfree (respline);
645       return FTPINVPASV;
646     }
647
648   if (!*s || *s++ != ',')
649     {
650       xfree (respline);
651       return FTPINVPASV;
652     }
653
654   /* Then, get the address length */
655   addrlen = 0;
656   for (; ISDIGIT (*s); s++)
657     addrlen = (*s - '0') + 10 * addrlen;
658
659   if (!*s || *s++ != ',')
660     {
661       xfree (respline);
662       return FTPINVPASV;
663     }
664
665   if (addrlen > 16)
666     {
667       xfree (respline);
668       return FTPINVPASV;
669     }
670
671   if ((af == 4 && addrlen != 4)
672       || (af == 6 && addrlen != 16))
673     {
674       xfree (respline);
675       return FTPINVPASV;
676     }
677
678   /* Now, we get the actual address */
679   for (i = 0; i < addrlen; i++)
680     {
681       tmp[i] = 0;
682       for (; ISDIGIT (*s); s++)
683         tmp[i] = (*s - '0') + 10 * tmp[i];
684       if (*s == ',')
685         s++;
686       else
687         {
688           xfree (respline);
689           return FTPINVPASV;
690         }
691     }
692
693   /* Now, get the port length */
694   portlen = 0;
695   for (; ISDIGIT (*s); s++)
696     portlen = (*s - '0') + 10 * portlen;
697
698   if (!*s || *s++ != ',')
699     {
700       xfree (respline);
701       return FTPINVPASV;
702     }
703
704   if (portlen > 2)
705     {
706       xfree (respline);
707       return FTPINVPASV;
708     }
709
710   /* Finally, we get the port number */
711   tmpprt[0] = 0;
712   for (; ISDIGIT (*s); s++)
713     tmpprt[0] = (*s - '0') + 10 * tmpprt[0];
714
715   if (!*s || *s++ != ',')
716     {
717       xfree (respline);
718       return FTPINVPASV;
719     }
720
721   tmpprt[1] = 0;
722   for (; ISDIGIT (*s); s++)
723     tmpprt[1] = (*s - '0') + 10 * tmpprt[1];
724
725   assert (s != NULL);
726
727   if (af == 4)
728     {
729       addr->type = IPV4_ADDRESS;
730       memcpy (ADDRESS_IPV4_DATA (addr), tmp, 4);
731       *port = ((tmpprt[0] << 8) & 0xff00) + tmpprt[1];
732       DEBUGP (("lpsv addr is: %s\n", pretty_print_address(addr)));
733       DEBUGP (("tmpprt[0] is: %d\n", tmpprt[0]));
734       DEBUGP (("tmpprt[1] is: %d\n", tmpprt[1]));
735       DEBUGP (("*port is: %d\n", *port));
736     }
737   else
738     {
739       assert (af == 6);
740       addr->type = IPV6_ADDRESS;
741       memcpy (ADDRESS_IPV6_DATA (addr), tmp, 16);
742       *port = ((tmpprt[0] << 8) & 0xff00) + tmpprt[1];
743       DEBUGP (("lpsv addr is: %s\n", pretty_print_address(addr)));
744       DEBUGP (("tmpprt[0] is: %d\n", tmpprt[0]));
745       DEBUGP (("tmpprt[1] is: %d\n", tmpprt[1]));
746       DEBUGP (("*port is: %d\n", *port));
747     }
748
749   xfree (respline);
750   return FTPOK;
751 }
752
753 /* Similar to ftp_eprt, but uses `EPSV' to initiate the passive FTP
754    transfer.  Reads the response from server and parses it.  Reads the
755    host and port addresses and returns them.  */
756 uerr_t
757 ftp_epsv (struct rbuf *rbuf, ip_address *ip, int *port)
758 {
759   char *request, *respline, *start, delim, *s;
760   int nwritten, i;
761   uerr_t err;
762   int tport;
763   socklen_t addrlen;
764   struct sockaddr_storage ss;
765   struct sockaddr *sa = (struct sockaddr *)&ss;
766
767   assert (rbuf != NULL);
768   assert (rbuf_initialized_p(rbuf));
769   assert (ip != NULL);
770   assert (port != NULL);
771
772   addrlen = sizeof (ss);
773   if (getpeername (rbuf->fd, sa, &addrlen) < 0)
774     /* Mauro Tortonesi: HOW DO WE HANDLE THIS ERROR? */
775     return CONPORTERR;
776
777   assert (sa->sa_family == AF_INET || sa->sa_family == AF_INET6);
778
779   sockaddr_get_data (sa, ip, NULL);
780
781   /* Form the request.  */
782   /* EPSV 1 means that we ask for IPv4 and EPSV 2 means that we ask for IPv6. */
783   request = ftp_request ("EPSV", (sa->sa_family == AF_INET ? "1" : "2"));
784
785   /* And send it.  */
786   nwritten = iwrite (RBUF_FD (rbuf), request, strlen (request));
787   if (nwritten < 0)
788     {
789       xfree (request);
790       return WRITEFAILED;
791     }
792   xfree (request);
793
794   /* Get the server response.  */
795   err = ftp_response (rbuf, &respline);
796   if (err != FTPOK)
797     {
798       xfree (respline);
799       return err;
800     }
801   if (*respline != '2')
802     {
803       xfree (respline);
804       return FTPNOPASV;
805     }  
806
807   assert (respline != NULL);
808
809   DEBUGP(("respline is %s\n", respline));
810
811   /* Parse the response.  */
812   s = respline;
813
814   /* Skip the useless stuff and get what's inside the parentheses */
815   start = strchr (respline, '(');
816   if (start == NULL)
817     {
818       xfree (respline);
819       return FTPINVPASV;
820     }  
821
822   /* Skip the first two void fields */
823   s = start + 1;
824   delim = *s++;
825   if (delim < 33 || delim > 126)
826     {
827       xfree (respline);
828       return FTPINVPASV;
829     }  
830
831   for (i = 0; i < 2; i++)
832     {
833       if (*s++ != delim) 
834         {
835           xfree (respline);
836         return FTPINVPASV;
837         }  
838     }
839
840   /* Finally, get the port number */
841   tport = 0; 
842   for (i = 1; ISDIGIT (*s); s++) 
843     {
844       if (i > 5)
845         {
846           xfree (respline);
847           return FTPINVPASV;
848         }  
849       tport = (*s - '0') + 10 * tport;
850     }
851
852   /* Make sure that the response terminates correcty */
853   if (*s++ != delim)
854     {
855       xfree (respline);
856       return FTPINVPASV;
857     }  
858
859   if (*s++ != ')')
860     {
861       xfree (respline);
862       return FTPINVPASV;
863     }  
864
865   *port = tport;
866
867   xfree (respline);
868   return FTPOK;
869 }
870 #endif
871
872 /* Sends the TYPE request to the server.  */
873 uerr_t
874 ftp_type (struct rbuf *rbuf, int type)
875 {
876   char *request, *respline;
877   int nwritten;
878   uerr_t err;
879   char stype[2];
880
881   /* Construct argument.  */
882   stype[0] = type;
883   stype[1] = 0;
884   /* Send TYPE request.  */
885   request = ftp_request ("TYPE", stype);
886   nwritten = iwrite (RBUF_FD (rbuf), request, strlen (request));
887   if (nwritten < 0)
888     {
889       xfree (request);
890       return WRITEFAILED;
891     }
892   xfree (request);
893   /* Get appropriate response.  */
894   err = ftp_response (rbuf, &respline);
895   if (err != FTPOK)
896     {
897       xfree (respline);
898       return err;
899     }
900   if (*respline != '2')
901     {
902       xfree (respline);
903       return FTPUNKNOWNTYPE;
904     }
905   xfree (respline);
906   /* All OK.  */
907   return FTPOK;
908 }
909
910 /* Changes the working directory by issuing a CWD command to the
911    server.  */
912 uerr_t
913 ftp_cwd (struct rbuf *rbuf, const char *dir)
914 {
915   char *request, *respline;
916   int nwritten;
917   uerr_t err;
918
919   /* Send CWD request.  */
920   request = ftp_request ("CWD", dir);
921   nwritten = iwrite (RBUF_FD (rbuf), request, strlen (request));
922   if (nwritten < 0)
923     {
924       xfree (request);
925       return WRITEFAILED;
926     }
927   xfree (request);
928   /* Get appropriate response.  */
929   err = ftp_response (rbuf, &respline);
930   if (err != FTPOK)
931     {
932       xfree (respline);
933       return err;
934     }
935   if (*respline == '5')
936     {
937       xfree (respline);
938       return FTPNSFOD;
939     }
940   if (*respline != '2')
941     {
942       xfree (respline);
943       return FTPRERR;
944     }
945   xfree (respline);
946   /* All OK.  */
947   return FTPOK;
948 }
949
950 /* Sends REST command to the FTP server.  */
951 uerr_t
952 ftp_rest (struct rbuf *rbuf, long offset)
953 {
954   char *request, *respline;
955   int nwritten;
956   uerr_t err;
957   static char numbuf[24]; /* Buffer for the number */
958
959   number_to_string (numbuf, offset);
960   request = ftp_request ("REST", numbuf);
961   nwritten = iwrite (RBUF_FD (rbuf), request, strlen (request));
962   if (nwritten < 0)
963     {
964       xfree (request);
965       return WRITEFAILED;
966     }
967   xfree (request);
968   /* Get appropriate response.  */
969   err = ftp_response (rbuf, &respline);
970   if (err != FTPOK)
971     {
972       xfree (respline);
973       return err;
974     }
975   if (*respline != '3')
976     {
977       xfree (respline);
978       return FTPRESTFAIL;
979     }
980   xfree (respline);
981   /* All OK.  */
982   return FTPOK;
983 }
984
985 /* Sends RETR command to the FTP server.  */
986 uerr_t
987 ftp_retr (struct rbuf *rbuf, const char *file)
988 {
989   char *request, *respline;
990   int nwritten;
991   uerr_t err;
992
993   /* Send RETR request.  */
994   request = ftp_request ("RETR", file);
995   nwritten = iwrite (RBUF_FD (rbuf), request, strlen (request));
996   if (nwritten < 0)
997     {
998       xfree (request);
999       return WRITEFAILED;
1000     }
1001   xfree (request);
1002   /* Get appropriate response.  */
1003   err = ftp_response (rbuf, &respline);
1004   if (err != FTPOK)
1005     {
1006       xfree (respline);
1007       return err;
1008     }
1009   if (*respline == '5')
1010     {
1011       xfree (respline);
1012       return FTPNSFOD;
1013     }
1014   if (*respline != '1')
1015     {
1016       xfree (respline);
1017       return FTPRERR;
1018     }
1019   xfree (respline);
1020   /* All OK.  */
1021   return FTPOK;
1022 }
1023
1024 /* Sends the LIST command to the server.  If FILE is NULL, send just
1025    `LIST' (no space).  */
1026 uerr_t
1027 ftp_list (struct rbuf *rbuf, const char *file)
1028 {
1029   char *request, *respline;
1030   int nwritten;
1031   uerr_t err;
1032
1033   /* Send LIST request.  */
1034   request = ftp_request ("LIST", file);
1035   nwritten = iwrite (RBUF_FD (rbuf), request, strlen (request));
1036   if (nwritten < 0)
1037     {
1038       xfree (request);
1039       return WRITEFAILED;
1040     }
1041   xfree (request);
1042   /* Get appropriate respone.  */
1043   err = ftp_response (rbuf, &respline);
1044   if (err != FTPOK)
1045     {
1046       xfree (respline);
1047       return err;
1048     }
1049   if (*respline == '5')
1050     {
1051       xfree (respline);
1052       return FTPNSFOD;
1053     }
1054   if (*respline != '1')
1055     {
1056       xfree (respline);
1057       return FTPRERR;
1058     }
1059   xfree (respline);
1060   /* All OK.  */
1061   return FTPOK;
1062 }
1063
1064 /* Sends the SYST command to the server. */
1065 uerr_t
1066 ftp_syst (struct rbuf *rbuf, enum stype *server_type)
1067 {
1068   char *request, *respline;
1069   int nwritten;
1070   uerr_t err;
1071
1072   /* Send SYST request.  */
1073   request = ftp_request ("SYST", NULL);
1074   nwritten = iwrite (RBUF_FD (rbuf), request, strlen (request));
1075   if (nwritten < 0)
1076     {
1077       xfree (request);
1078       return WRITEFAILED;
1079     }
1080   xfree (request);
1081
1082   /* Get appropriate response.  */
1083   err = ftp_response (rbuf, &respline);
1084   if (err != FTPOK)
1085     {
1086       xfree (respline);
1087       return err;
1088     }
1089   if (*respline == '5')
1090     {
1091       xfree (respline);
1092       return FTPSRVERR;
1093     }
1094
1095   /* Skip the number (215, but 200 (!!!) in case of VMS) */
1096   strtok (respline, " ");
1097
1098   /* Which system type has been reported (we are interested just in the
1099      first word of the server response)?  */
1100   request = strtok (NULL, " ");
1101
1102   if (!strcasecmp (request, "VMS"))
1103     *server_type = ST_VMS;
1104   else if (!strcasecmp (request, "UNIX"))
1105     *server_type = ST_UNIX;
1106   else if (!strcasecmp (request, "WINDOWS_NT"))
1107     *server_type = ST_WINNT;
1108   else if (!strcasecmp (request, "MACOS"))
1109     *server_type = ST_MACOS;
1110   else if (!strcasecmp (request, "OS/400"))
1111     *server_type = ST_OS400;
1112   else
1113     *server_type = ST_OTHER;
1114
1115   xfree (respline);
1116   /* All OK.  */
1117   return FTPOK;
1118 }
1119
1120 /* Sends the PWD command to the server. */
1121 uerr_t
1122 ftp_pwd (struct rbuf *rbuf, char **pwd)
1123 {
1124   char *request, *respline;
1125   int nwritten;
1126   uerr_t err;
1127
1128   /* Send PWD request.  */
1129   request = ftp_request ("PWD", NULL);
1130   nwritten = iwrite (RBUF_FD (rbuf), request, strlen (request));
1131   if (nwritten < 0)
1132     {
1133       xfree (request);
1134       return WRITEFAILED;
1135     }
1136   xfree (request);
1137   /* Get appropriate response.  */
1138   err = ftp_response (rbuf, &respline);
1139   if (err != FTPOK)
1140     {
1141       xfree (respline);
1142       return err;
1143     }
1144   if (*respline == '5')
1145     {
1146       xfree (respline);
1147       return FTPSRVERR;
1148     }
1149
1150   /* Skip the number (257), leading citation mark, trailing citation mark
1151      and everything following it. */
1152   strtok (respline, "\"");
1153   request = strtok (NULL, "\"");
1154
1155   /* Has the `pwd' been already allocated?  Free! */
1156   FREE_MAYBE (*pwd);
1157
1158   *pwd = xstrdup (request);
1159
1160   xfree (respline);
1161   /* All OK.  */
1162   return FTPOK;
1163 }
1164
1165 /* Sends the SIZE command to the server, and returns the value in 'size'.
1166  * If an error occurs, size is set to zero. */
1167 uerr_t
1168 ftp_size (struct rbuf *rbuf, const char *file, long int *size)
1169 {
1170   char *request, *respline;
1171   int nwritten;
1172   uerr_t err;
1173
1174   /* Send PWD request.  */
1175   request = ftp_request ("SIZE", file);
1176   nwritten = iwrite (RBUF_FD (rbuf), request, strlen (request));
1177   if (nwritten < 0)
1178     {
1179       xfree (request);
1180       *size = 0;
1181       return WRITEFAILED;
1182     }
1183   xfree (request);
1184   /* Get appropriate response.  */
1185   err = ftp_response (rbuf, &respline);
1186   if (err != FTPOK)
1187     {
1188       xfree (respline);
1189       *size = 0;
1190       return err;
1191     }
1192   if (*respline == '5')
1193     {
1194       /* 
1195        * Probably means SIZE isn't supported on this server.
1196        * Error is nonfatal since SIZE isn't in RFC 959 
1197        */
1198       xfree (respline);
1199       *size = 0;
1200       return FTPOK;
1201     }
1202
1203   errno = 0;
1204   *size = strtol (respline + 4, NULL, 0);
1205   if (errno) 
1206     {
1207       /* 
1208        * Couldn't parse the response for some reason.  On the (few)
1209        * tests I've done, the response is 213 <SIZE> with nothing else -
1210        * maybe something a bit more resilient is necessary.  It's not a
1211        * fatal error, however.
1212        */
1213       xfree (respline);
1214       *size = 0;
1215       return FTPOK;
1216     }
1217
1218   xfree (respline);
1219   /* All OK.  */
1220   return FTPOK;
1221 }
1222
1223 /* If URL's params are of the form "type=X", return character X.
1224    Otherwise, return 'I' (the default type).  */
1225 char
1226 ftp_process_type (const char *params)
1227 {
1228   if (params
1229       && 0 == strncasecmp (params, "type=", 5)
1230       && params[5] != '\0')
1231     return TOUPPER (params[5]);
1232   else
1233     return 'I';
1234 }