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