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