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