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