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