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