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