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