summaryrefslogtreecommitdiffstats
path: root/src/ftp-basic.c
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-10 20:25:44 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-10 20:25:44 +0000
commitb3925d944ed94cc76bbcbb14a799ec9beeb8d1bf (patch)
treea5e5ccdbc84294390695b5ae3a8c89cc16e6cbae /src/ftp-basic.c
parentInitial commit. (diff)
downloadwget-b3925d944ed94cc76bbcbb14a799ec9beeb8d1bf.tar.xz
wget-b3925d944ed94cc76bbcbb14a799ec9beeb8d1bf.zip
Adding upstream version 1.21.4.upstream/1.21.4
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'src/ftp-basic.c')
-rw-r--r--src/ftp-basic.c1342
1 files changed, 1342 insertions, 0 deletions
diff --git a/src/ftp-basic.c b/src/ftp-basic.c
new file mode 100644
index 0000000..d999027
--- /dev/null
+++ b/src/ftp-basic.c
@@ -0,0 +1,1342 @@
+/* Basic FTP routines.
+ Copyright (C) 1996-2011, 2014-2015, 2018-2023 Free Software
+ Foundation, Inc.
+
+This file is part of GNU Wget.
+
+GNU Wget is free software; you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+GNU Wget is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with Wget. If not, see <http://www.gnu.org/licenses/>.
+
+Additional permission under GNU GPL version 3 section 7
+
+If you modify this program, or any covered work, by linking or
+combining it with the OpenSSL project's OpenSSL library (or a
+modified version of that library), containing parts covered by the
+terms of the OpenSSL or SSLeay licenses, the Free Software Foundation
+grants you additional permission to convey the resulting work.
+Corresponding Source for a non-source form of such a combination
+shall include the source code for the parts of OpenSSL used as well
+as that of the covered work. */
+
+#include "wget.h"
+
+#include <assert.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <errno.h>
+
+#include <string.h>
+#include <unistd.h>
+#include "utils.h"
+#include "connect.h"
+#include "host.h"
+#include "ftp.h"
+#include "retr.h"
+#include "c-strcase.h"
+
+
+/* Get the response of FTP server and allocate enough room to handle
+ it. <CR> and <LF> characters are stripped from the line, and the
+ line is 0-terminated. All the response lines but the last one are
+ skipped. The last line is determined as described in RFC959.
+
+ If the line is successfully read, FTPOK is returned, and *ret_line
+ is assigned a freshly allocated line. Otherwise, FTPRERR is
+ returned, and the value of *ret_line should be ignored. */
+
+uerr_t
+ftp_response (int fd, char **ret_line)
+{
+ for (;;)
+ {
+ char *p;
+ char *line = fd_read_line (fd);
+ if (!line)
+ return FTPRERR;
+
+ /* Strip trailing CRLF before printing the line, so that
+ quoting doesn't include bogus \012 and \015. */
+ if ((p = strpbrk(line , "\r\n")))
+ *p = 0;
+
+ if (opt.server_response)
+ logprintf (LOG_NOTQUIET, "%s\n",
+ quotearg_style (escape_quoting_style, line));
+ else
+ DEBUGP (("%s\n", quotearg_style (escape_quoting_style, line)));
+
+ /* The last line of output is the one that begins with "ddd ". */
+ if (c_isdigit (line[0]) && c_isdigit (line[1]) && c_isdigit (line[2])
+ && line[3] == ' ')
+ {
+ *ret_line = line;
+ return FTPOK;
+ }
+ xfree (line);
+ }
+}
+
+/* Returns the malloc-ed FTP request, ending with <CR><LF>, printing
+ it if printing is required. If VALUE is NULL, just use
+ command<CR><LF>. */
+static char *
+ftp_request (const char *command, const char *value)
+{
+ char *res;
+
+ if (value)
+ {
+ char *defanged = NULL, buf[256];
+
+ /* Check for newlines in VALUE (possibly injected by the %0A URL
+ escape) making the callers inadvertently send multiple FTP
+ commands at once. Without this check an attacker could
+ intentionally redirect to ftp://server/fakedir%0Acommand.../
+ and execute arbitrary FTP command on a remote FTP server. */
+ if (strpbrk (value, "\r\n"))
+ {
+ /* Copy VALUE to the stack and modify CR/LF to space. */
+ char *p;
+ size_t len = strlen(value);
+
+ if (len < sizeof (buf))
+ defanged = buf;
+ else
+ defanged = xmalloc (len + 1);
+
+ memcpy (defanged, value, len + 1);
+
+ for (p = defanged; *p; p++)
+ if (*p == '\r' || *p == '\n')
+ *p = ' ';
+
+ DEBUGP (("\nDetected newlines in %s \"%s\"; changing to %s \"%s\"\n",
+ command, quotearg_style (escape_quoting_style, value),
+ command, quotearg_style (escape_quoting_style, defanged)));
+
+ /* Make VALUE point to the defanged copy of the string. */
+ value = defanged;
+ }
+ res = concat_strings (command, " ", value, "\r\n", (char *) 0);
+
+ if (defanged != buf)
+ xfree (defanged);
+ }
+ else
+ res = concat_strings (command, "\r\n", (char *) 0);
+ if (opt.server_response)
+ {
+ /* Hack: don't print out password. */
+ if (strncmp (res, "PASS", 4) != 0)
+ logprintf (LOG_ALWAYS, "--> %s\n", res);
+ else
+ logputs (LOG_ALWAYS, "--> PASS Turtle Power!\n\n");
+ }
+ else
+ DEBUGP (("\n--> %s\n", res));
+ return res;
+}
+
+uerr_t
+ftp_greeting (int csock)
+{
+ uerr_t err = FTPOK;
+ char *response = NULL;
+
+ err = ftp_response (csock, &response);
+ if (err != FTPOK)
+ goto bail;
+ if (*response != '2')
+ err = FTPSRVERR;
+
+bail:
+ if (response)
+ xfree (response);
+ return err;
+}
+/* Sends the USER and PASS commands to the server, to control
+ connection socket csock. */
+uerr_t
+ftp_login (int csock, const char *acc, const char *pass)
+{
+ uerr_t err;
+ char *request, *respline;
+ int nwritten;
+
+ /* Send USER username. */
+ request = ftp_request ("USER", acc);
+ nwritten = fd_write (csock, request, strlen (request), -1);
+ if (nwritten < 0)
+ {
+ xfree (request);
+ return WRITEFAILED;
+ }
+ xfree (request);
+ /* Get appropriate response. */
+ err = ftp_response (csock, &respline);
+ if (err != FTPOK)
+ return err;
+ /* An unprobable possibility of logging without a password. */
+ if (*respline == '2')
+ {
+ xfree (respline);
+ return FTPOK;
+ }
+ /* Else, only response 3 is appropriate. */
+ if (*respline != '3')
+ {
+ xfree (respline);
+ return FTPLOGREFUSED;
+ }
+#ifdef ENABLE_OPIE
+ {
+ static const char *skey_head[] = {
+ "331 s/key ",
+ "331 opiekey "
+ };
+ size_t i;
+ const char *seed = NULL;
+
+ for (i = 0; i < countof (skey_head); i++)
+ {
+ int l = strlen (skey_head[i]);
+ if (0 == c_strncasecmp (skey_head[i], respline, l))
+ {
+ seed = respline + l;
+ break;
+ }
+ }
+ if (seed)
+ {
+ int skey_sequence = 0;
+
+ /* Extract the sequence from SEED. */
+ for (; c_isdigit (*seed); seed++)
+ skey_sequence = 10 * skey_sequence + *seed - '0';
+ if (*seed == ' ')
+ ++seed;
+ else
+ {
+ xfree (respline);
+ return FTPLOGREFUSED;
+ }
+ /* Replace the password with the SKEY response to the
+ challenge. */
+ pass = skey_response (skey_sequence, seed, pass);
+ }
+ }
+#endif /* ENABLE_OPIE */
+ xfree (respline);
+ /* Send PASS password. */
+ request = ftp_request ("PASS", pass);
+ nwritten = fd_write (csock, request, strlen (request), -1);
+ if (nwritten < 0)
+ {
+ xfree (request);
+ return WRITEFAILED;
+ }
+ xfree (request);
+ /* Get appropriate response. */
+ err = ftp_response (csock, &respline);
+ if (err != FTPOK)
+ return err;
+ if (*respline != '2')
+ {
+ xfree (respline);
+ return FTPLOGINC;
+ }
+ xfree (respline);
+ /* All OK. */
+ return FTPOK;
+}
+
+static void
+ip_address_to_port_repr (const ip_address *addr, int port, char *buf,
+ size_t buflen)
+{
+ unsigned char *ptr;
+
+ assert (addr->family == AF_INET);
+ /* buf must contain the argument of PORT (of the form a,b,c,d,e,f). */
+ assert (buflen >= 6 * 4);
+
+ ptr = IP_INADDR_DATA (addr);
+ snprintf (buf, buflen, "%d,%d,%d,%d,%d,%d", ptr[0], ptr[1],
+ ptr[2], ptr[3], (port & 0xff00) >> 8, port & 0xff);
+ buf[buflen - 1] = '\0';
+}
+
+/* Bind a port and send the appropriate PORT command to the FTP
+ server. Use acceptport after RETR, to get the socket of data
+ connection. */
+uerr_t
+ftp_port (int csock, int *local_sock)
+{
+ uerr_t err;
+ char *request, *respline;
+ ip_address addr;
+ int nwritten;
+ int port;
+ /* Must contain the argument of PORT (of the form a,b,c,d,e,f). */
+ char bytes[6 * 4 + 1];
+
+ /* Get the address of this side of the connection. */
+ if (!socket_ip_address (csock, &addr, ENDPOINT_LOCAL))
+ return FTPSYSERR;
+
+ assert (addr.family == AF_INET);
+
+ /* Setting port to 0 lets the system choose a free port. */
+ port = 0;
+
+ /* Bind the port. */
+ *local_sock = bind_local (&addr, &port);
+ if (*local_sock < 0)
+ return FTPSYSERR;
+
+ /* Construct the argument of PORT (of the form a,b,c,d,e,f). */
+ ip_address_to_port_repr (&addr, port, bytes, sizeof (bytes));
+
+ /* Send PORT request. */
+ request = ftp_request ("PORT", bytes);
+ nwritten = fd_write (csock, request, strlen (request), -1);
+ if (nwritten < 0)
+ {
+ xfree (request);
+ fd_close (*local_sock);
+ return WRITEFAILED;
+ }
+ xfree (request);
+
+ /* Get appropriate response. */
+ err = ftp_response (csock, &respline);
+ if (err != FTPOK)
+ {
+ fd_close (*local_sock);
+ return err;
+ }
+ if (*respline != '2')
+ {
+ xfree (respline);
+ fd_close (*local_sock);
+ return FTPPORTERR;
+ }
+ xfree (respline);
+ return FTPOK;
+}
+
+#ifdef ENABLE_IPV6
+static void
+ip_address_to_lprt_repr (const ip_address *addr, int port, char *buf,
+ size_t buflen)
+{
+ unsigned char *ptr = IP_INADDR_DATA (addr);
+
+ /* buf must contain the argument of LPRT (of the form af,n,h1,h2,...,hn,p1,p2). */
+ assert (buflen >= 21 * 4);
+
+ /* Construct the argument of LPRT (of the form af,n,h1,h2,...,hn,p1,p2). */
+ switch (addr->family)
+ {
+ case AF_INET:
+ snprintf (buf, buflen, "%d,%d,%d,%d,%d,%d,%d,%d,%d", 4, 4,
+ ptr[0], ptr[1], ptr[2], ptr[3], 2,
+ (port & 0xff00) >> 8, port & 0xff);
+ break;
+ case AF_INET6:
+ snprintf (buf, buflen,
+ "%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d",
+ 6, 16,
+ ptr[0], ptr[1], ptr[2], ptr[3], ptr[4], ptr[5], ptr[6], ptr[7],
+ ptr[8], ptr[9], ptr[10], ptr[11], ptr[12], ptr[13], ptr[14], ptr[15],
+ 2, (port & 0xff00) >> 8, port & 0xff);
+ break;
+ default:
+ abort ();
+ }
+}
+
+/* Bind a port and send the appropriate PORT command to the FTP
+ server. Use acceptport after RETR, to get the socket of data
+ connection. */
+uerr_t
+ftp_lprt (int csock, int *local_sock)
+{
+ uerr_t err;
+ char *request, *respline;
+ ip_address addr;
+ int nwritten;
+ int port;
+ /* Must contain the argument of LPRT (of the form af,n,h1,h2,...,hn,p1,p2). */
+ char bytes[21 * 4 + 1];
+
+ /* Get the address of this side of the connection. */
+ if (!socket_ip_address (csock, &addr, ENDPOINT_LOCAL))
+ return FTPSYSERR;
+
+ assert (addr.family == AF_INET || addr.family == AF_INET6);
+
+ /* Setting port to 0 lets the system choose a free port. */
+ port = 0;
+
+ /* Bind the port. */
+ *local_sock = bind_local (&addr, &port);
+ if (*local_sock < 0)
+ return FTPSYSERR;
+
+ /* Construct the argument of LPRT (of the form af,n,h1,h2,...,hn,p1,p2). */
+ ip_address_to_lprt_repr (&addr, port, bytes, sizeof (bytes));
+
+ /* Send PORT request. */
+ request = ftp_request ("LPRT", bytes);
+ nwritten = fd_write (csock, request, strlen (request), -1);
+ if (nwritten < 0)
+ {
+ xfree (request);
+ fd_close (*local_sock);
+ return WRITEFAILED;
+ }
+ xfree (request);
+ /* Get appropriate response. */
+ err = ftp_response (csock, &respline);
+ if (err != FTPOK)
+ {
+ fd_close (*local_sock);
+ return err;
+ }
+ if (*respline != '2')
+ {
+ xfree (respline);
+ fd_close (*local_sock);
+ return FTPPORTERR;
+ }
+ xfree (respline);
+ return FTPOK;
+}
+
+static void
+ip_address_to_eprt_repr (const ip_address *addr, int port, char *buf,
+ size_t buflen)
+{
+ int afnum;
+
+ /* buf must contain the argument of EPRT (of the form |af|addr|port|).
+ * 4 chars for the | separators, INET6_ADDRSTRLEN chars for addr
+ * 1 char for af (1-2) and 5 chars for port (0-65535) */
+ assert (buflen >= 4 + INET6_ADDRSTRLEN + 1 + 5);
+
+ /* Construct the argument of EPRT (of the form |af|addr|port|). */
+ afnum = (addr->family == AF_INET ? 1 : 2);
+ snprintf (buf, buflen, "|%d|%s|%d|", afnum, print_address (addr), port);
+ buf[buflen - 1] = '\0';
+}
+
+/* Bind a port and send the appropriate PORT command to the FTP
+ server. Use acceptport after RETR, to get the socket of data
+ connection. */
+uerr_t
+ftp_eprt (int csock, int *local_sock)
+{
+ uerr_t err;
+ char *request, *respline;
+ ip_address addr;
+ int nwritten;
+ int port;
+ /* Must contain the argument of EPRT (of the form |af|addr|port|).
+ * 4 chars for the | separators, INET6_ADDRSTRLEN chars for addr
+ * 1 char for af (1-2) and 5 chars for port (0-65535) */
+ char bytes[4 + INET6_ADDRSTRLEN + 1 + 5 + 1];
+
+ /* Get the address of this side of the connection. */
+ if (!socket_ip_address (csock, &addr, ENDPOINT_LOCAL))
+ return FTPSYSERR;
+
+ /* Setting port to 0 lets the system choose a free port. */
+ port = 0;
+
+ /* Bind the port. */
+ *local_sock = bind_local (&addr, &port);
+ if (*local_sock < 0)
+ return FTPSYSERR;
+
+ /* Construct the argument of EPRT (of the form |af|addr|port|). */
+ ip_address_to_eprt_repr (&addr, port, bytes, sizeof (bytes));
+
+ /* Send PORT request. */
+ request = ftp_request ("EPRT", bytes);
+ nwritten = fd_write (csock, request, strlen (request), -1);
+ if (nwritten < 0)
+ {
+ xfree (request);
+ fd_close (*local_sock);
+ return WRITEFAILED;
+ }
+ xfree (request);
+ /* Get appropriate response. */
+ err = ftp_response (csock, &respline);
+ if (err != FTPOK)
+ {
+ fd_close (*local_sock);
+ return err;
+ }
+ if (*respline != '2')
+ {
+ xfree (respline);
+ fd_close (*local_sock);
+ return FTPPORTERR;
+ }
+ xfree (respline);
+ return FTPOK;
+}
+#endif
+
+#ifdef HAVE_SSL
+/*
+ * The following three functions defined into this #ifdef block
+ * wrap the extended FTP commands defined in RFC 2228 (FTP Security Extensions).
+ * Currently, only FTPS is supported, so these functions are only compiled when SSL
+ * support is available, because there's no point in using FTPS when there's no SSL.
+ * Shall someone add new secure FTP protocols in the future, feel free to remove this
+ * #ifdef, or add new constants to it.
+ */
+
+/*
+ * Sends an AUTH command as defined by RFC 2228,
+ * deriving its argument from the scheme. For example, if the provided scheme
+ * is SCHEME_FTPS, the command sent will be "AUTH TLS". Currently, this is the only
+ * scheme supported, so this function will return FTPNOAUTH when supplied a different
+ * one. It will also return FTPNOAUTH if the target server does not support FTPS.
+ */
+uerr_t
+ftp_auth (int csock, enum url_scheme scheme)
+{
+ uerr_t err = 0;
+ int written = 0;
+ char *request = NULL, *response = NULL;
+
+ if (scheme == SCHEME_FTPS)
+ {
+ request = ftp_request ("AUTH", "TLS");
+ written = fd_write (csock, request, strlen (request), -1);
+ if (written < 0)
+ {
+ err = WRITEFAILED;
+ goto bail;
+ }
+ err = ftp_response (csock, &response);
+ if (err != FTPOK)
+ goto bail;
+ if (*response != '2')
+ err = FTPNOAUTH;
+ }
+ else
+ err = FTPNOAUTH;
+
+bail:
+ xfree (request);
+ xfree (response);
+
+ return err;
+}
+
+uerr_t
+ftp_pbsz (int csock, int pbsz)
+{
+ uerr_t err = 0;
+ int written = 0;
+ char spbsz[5];
+ char *request = NULL, *response = NULL;
+
+ snprintf (spbsz, 5, "%d", pbsz);
+ request = ftp_request ("PBSZ", spbsz);
+ written = fd_write (csock, request, strlen (request), -1);
+ if (written < 0)
+ {
+ err = WRITEFAILED;
+ goto bail;
+ }
+
+ err = ftp_response (csock, &response);
+ if (err != FTPOK)
+ goto bail;
+ if (*response != '2')
+ err = FTPNOPBSZ;
+
+bail:
+ xfree (request);
+ xfree (response);
+
+ return err;
+}
+
+uerr_t
+ftp_prot (int csock, enum prot_level prot)
+{
+ uerr_t err = 0;
+ int written = 0;
+ char *request = NULL, *response = NULL;
+ /* value must be a single character value */
+ char value[2];
+
+ value[0] = prot;
+ value[1] = '\0';
+
+ request = ftp_request ("PROT", value);
+ written = fd_write (csock, request, strlen (request), -1);
+ if (written < 0)
+ {
+ err = WRITEFAILED;
+ goto bail;
+ }
+
+ err = ftp_response (csock, &response);
+ if (err != FTPOK)
+ goto bail;
+ if (*response != '2')
+ err = FTPNOPROT;
+
+bail:
+ xfree (request);
+ xfree (response);
+
+ return err;
+}
+#endif /* HAVE_SSL */
+
+/* Similar to ftp_port, but uses `PASV' to initiate the passive FTP
+ transfer. Reads the response from server and parses it. Reads the
+ host and port addresses and returns them. */
+uerr_t
+ftp_pasv (int csock, ip_address *addr, int *port)
+{
+ char *request, *respline, *s;
+ int nwritten, i;
+ uerr_t err;
+ unsigned char tmp[6];
+
+ assert (addr != NULL);
+ assert (port != NULL);
+
+ xzero (*addr);
+
+ /* Form the request. */
+ request = ftp_request ("PASV", NULL);
+ /* And send it. */
+ nwritten = fd_write (csock, request, strlen (request), -1);
+ if (nwritten < 0)
+ {
+ xfree (request);
+ return WRITEFAILED;
+ }
+ xfree (request);
+ /* Get the server response. */
+ err = ftp_response (csock, &respline);
+ if (err != FTPOK)
+ return err;
+ if (*respline != '2')
+ {
+ xfree (respline);
+ return FTPNOPASV;
+ }
+ /* Parse the request. */
+ s = respline;
+ for (s += 4; *s && !c_isdigit (*s); s++)
+ ;
+ if (!*s)
+ {
+ xfree (respline);
+ return FTPINVPASV;
+ }
+ for (i = 0; i < 6; i++)
+ {
+ tmp[i] = 0;
+ for (; c_isdigit (*s); s++)
+ tmp[i] = (*s - '0') + 10 * tmp[i];
+ if (*s == ',')
+ s++;
+ else if (i < 5)
+ {
+ /* When on the last number, anything can be a terminator. */
+ xfree (respline);
+ return FTPINVPASV;
+ }
+ }
+ xfree (respline);
+
+ addr->family = AF_INET;
+ memcpy (IP_INADDR_DATA (addr), tmp, 4);
+ *port = ((tmp[4] << 8) & 0xff00) + tmp[5];
+
+ return FTPOK;
+}
+
+#ifdef ENABLE_IPV6
+/* Similar to ftp_lprt, but uses `LPSV' to initiate the passive FTP
+ transfer. Reads the response from server and parses it. Reads the
+ host and port addresses and returns them. */
+uerr_t
+ftp_lpsv (int csock, ip_address *addr, int *port)
+{
+ char *request, *respline, *s;
+ int nwritten, i, af, addrlen, portlen;
+ uerr_t err;
+ unsigned char tmp[16];
+ unsigned char tmpprt[2];
+
+ assert (addr != NULL);
+ assert (port != NULL);
+
+ xzero (*addr);
+
+ /* Form the request. */
+ request = ftp_request ("LPSV", NULL);
+
+ /* And send it. */
+ nwritten = fd_write (csock, request, strlen (request), -1);
+ if (nwritten < 0)
+ {
+ xfree (request);
+ return WRITEFAILED;
+ }
+ xfree (request);
+
+ /* Get the server response. */
+ err = ftp_response (csock, &respline);
+ if (err != FTPOK)
+ return err;
+ if (*respline != '2')
+ {
+ xfree (respline);
+ return FTPNOPASV;
+ }
+
+ /* Parse the response. */
+ s = respline;
+ for (s += 4; *s && !c_isdigit (*s); s++)
+ ;
+ if (!*s)
+ {
+ xfree (respline);
+ return FTPINVPASV;
+ }
+
+ /* First, get the address family */
+ af = 0;
+ for (; c_isdigit (*s); s++)
+ af = (*s - '0') + 10 * af;
+
+ if (af != 4 && af != 6)
+ {
+ xfree (respline);
+ return FTPINVPASV;
+ }
+
+ if (!*s || *s++ != ',')
+ {
+ xfree (respline);
+ return FTPINVPASV;
+ }
+
+ /* Then, get the address length */
+ addrlen = 0;
+ for (; c_isdigit (*s); s++)
+ addrlen = (*s - '0') + 10 * addrlen;
+
+ if (!*s || *s++ != ',')
+ {
+ xfree (respline);
+ return FTPINVPASV;
+ }
+
+ if (addrlen > 16)
+ {
+ xfree (respline);
+ return FTPINVPASV;
+ }
+
+ if ((af == 4 && addrlen != 4)
+ || (af == 6 && addrlen != 16))
+ {
+ xfree (respline);
+ return FTPINVPASV;
+ }
+
+ /* Now, we get the actual address */
+ for (i = 0; i < addrlen; i++)
+ {
+ tmp[i] = 0;
+ for (; c_isdigit (*s); s++)
+ tmp[i] = (*s - '0') + 10 * tmp[i];
+ if (*s == ',')
+ s++;
+ else
+ {
+ xfree (respline);
+ return FTPINVPASV;
+ }
+ }
+
+ /* Now, get the port length */
+ portlen = 0;
+ for (; c_isdigit (*s); s++)
+ portlen = (*s - '0') + 10 * portlen;
+
+ if (!*s || *s++ != ',')
+ {
+ xfree (respline);
+ return FTPINVPASV;
+ }
+
+ if (portlen > 2)
+ {
+ xfree (respline);
+ return FTPINVPASV;
+ }
+
+ /* Finally, we get the port number */
+ tmpprt[0] = 0;
+ for (; c_isdigit (*s); s++)
+ tmpprt[0] = (*s - '0') + 10 * tmpprt[0];
+
+ if (!*s || *s++ != ',')
+ {
+ xfree (respline);
+ return FTPINVPASV;
+ }
+
+ tmpprt[1] = 0;
+ for (; c_isdigit (*s); s++)
+ tmpprt[1] = (*s - '0') + 10 * tmpprt[1];
+
+ assert (s != NULL);
+
+ if (af == 4)
+ {
+ addr->family = AF_INET;
+ memcpy (IP_INADDR_DATA (addr), tmp, 4);
+ *port = ((tmpprt[0] << 8) & 0xff00) + tmpprt[1];
+ DEBUGP (("lpsv addr is: %s\n", print_address(addr)));
+ DEBUGP (("tmpprt[0] is: %d\n", tmpprt[0]));
+ DEBUGP (("tmpprt[1] is: %d\n", tmpprt[1]));
+ DEBUGP (("*port is: %d\n", *port));
+ }
+ else
+ {
+ assert (af == 6);
+ addr->family = AF_INET6;
+ memcpy (IP_INADDR_DATA (addr), tmp, 16);
+ *port = ((tmpprt[0] << 8) & 0xff00) + tmpprt[1];
+ DEBUGP (("lpsv addr is: %s\n", print_address(addr)));
+ DEBUGP (("tmpprt[0] is: %d\n", tmpprt[0]));
+ DEBUGP (("tmpprt[1] is: %d\n", tmpprt[1]));
+ DEBUGP (("*port is: %d\n", *port));
+ }
+
+ xfree (respline);
+ return FTPOK;
+}
+
+/* Similar to ftp_eprt, but uses `EPSV' to initiate the passive FTP
+ transfer. Reads the response from server and parses it. Reads the
+ host and port addresses and returns them. */
+uerr_t
+ftp_epsv (int csock, ip_address *ip, int *port)
+{
+ char *request, *respline, *start, delim, *s;
+ int nwritten, i;
+ uerr_t err;
+ int tport;
+
+ assert (ip != NULL);
+ assert (port != NULL);
+
+ /* IP already contains the IP address of the control connection's
+ peer, so we don't need to call socket_ip_address here. */
+
+ /* Form the request. */
+ /* EPSV 1 means that we ask for IPv4 and EPSV 2 means that we ask for IPv6. */
+ request = ftp_request ("EPSV", (ip->family == AF_INET ? "1" : "2"));
+
+ /* And send it. */
+ nwritten = fd_write (csock, request, strlen (request), -1);
+ if (nwritten < 0)
+ {
+ xfree (request);
+ return WRITEFAILED;
+ }
+ xfree (request);
+
+ /* Get the server response. */
+ err = ftp_response (csock, &respline);
+ if (err != FTPOK)
+ return err;
+ if (*respline != '2')
+ {
+ xfree (respline);
+ return FTPNOPASV;
+ }
+
+ assert (respline != NULL);
+
+ DEBUGP(("respline is %s\n", respline));
+
+ /* Skip the useless stuff and get what's inside the parentheses */
+ start = strchr (respline, '(');
+ if (start == NULL)
+ {
+ xfree (respline);
+ return FTPINVPASV;
+ }
+
+ /* Skip the first two void fields */
+ s = start + 1;
+ delim = *s++;
+ if (delim < 33 || delim > 126)
+ {
+ xfree (respline);
+ return FTPINVPASV;
+ }
+
+ for (i = 0; i < 2; i++)
+ {
+ if (*s++ != delim)
+ {
+ xfree (respline);
+ return FTPINVPASV;
+ }
+ }
+
+ /* Finally, get the port number */
+ for (tport = 0, i = 0; i < 5 && c_isdigit (*s); i++, s++)
+ tport = (*s - '0') + 10 * tport;
+
+ /* Make sure that the response terminates correctly */
+ if (*s++ != delim)
+ {
+ xfree (respline);
+ return FTPINVPASV;
+ }
+
+ if (*s != ')')
+ {
+ xfree (respline);
+ return FTPINVPASV;
+ }
+
+ *port = tport;
+
+ xfree (respline);
+ return FTPOK;
+}
+#endif
+
+/* Sends the TYPE request to the server. */
+uerr_t
+ftp_type (int csock, int type)
+{
+ char *request, *respline;
+ int nwritten;
+ uerr_t err;
+ char stype[2];
+
+ /* Construct argument. */
+ stype[0] = type;
+ stype[1] = 0;
+ /* Send TYPE request. */
+ request = ftp_request ("TYPE", stype);
+ nwritten = fd_write (csock, request, strlen (request), -1);
+ if (nwritten < 0)
+ {
+ xfree (request);
+ return WRITEFAILED;
+ }
+ xfree (request);
+ /* Get appropriate response. */
+ err = ftp_response (csock, &respline);
+ if (err != FTPOK)
+ return err;
+ if (*respline != '2')
+ {
+ xfree (respline);
+ return FTPUNKNOWNTYPE;
+ }
+ xfree (respline);
+ /* All OK. */
+ return FTPOK;
+}
+
+/* Changes the working directory by issuing a CWD command to the
+ server. */
+uerr_t
+ftp_cwd (int csock, const char *dir)
+{
+ char *request, *respline;
+ int nwritten;
+ uerr_t err;
+
+ /* Send CWD request. */
+ request = ftp_request ("CWD", dir);
+ nwritten = fd_write (csock, request, strlen (request), -1);
+ if (nwritten < 0)
+ {
+ xfree (request);
+ return WRITEFAILED;
+ }
+ xfree (request);
+ /* Get appropriate response. */
+ err = ftp_response (csock, &respline);
+ if (err != FTPOK)
+ return err;
+ if (*respline == '5')
+ {
+ xfree (respline);
+ return FTPNSFOD;
+ }
+ if (*respline != '2')
+ {
+ xfree (respline);
+ return FTPRERR;
+ }
+ xfree (respline);
+ /* All OK. */
+ return FTPOK;
+}
+
+/* Sends REST command to the FTP server. */
+uerr_t
+ftp_rest (int csock, wgint offset)
+{
+ char *request, *respline;
+ int nwritten;
+ uerr_t err;
+
+ request = ftp_request ("REST", number_to_static_string (offset));
+ nwritten = fd_write (csock, request, strlen (request), -1);
+ if (nwritten < 0)
+ {
+ xfree (request);
+ return WRITEFAILED;
+ }
+ xfree (request);
+ /* Get appropriate response. */
+ err = ftp_response (csock, &respline);
+ if (err != FTPOK)
+ return err;
+ if (*respline != '3')
+ {
+ xfree (respline);
+ return FTPRESTFAIL;
+ }
+ xfree (respline);
+ /* All OK. */
+ return FTPOK;
+}
+
+/* Sends RETR command to the FTP server. */
+uerr_t
+ftp_retr (int csock, const char *file)
+{
+ char *request, *respline;
+ int nwritten;
+ uerr_t err;
+
+ /* Send RETR request. */
+ request = ftp_request ("RETR", file);
+ nwritten = fd_write (csock, request, strlen (request), -1);
+ if (nwritten < 0)
+ {
+ xfree (request);
+ return WRITEFAILED;
+ }
+ xfree (request);
+ /* Get appropriate response. */
+ err = ftp_response (csock, &respline);
+ if (err != FTPOK)
+ return err;
+ if (*respline == '5')
+ {
+ xfree (respline);
+ return FTPNSFOD;
+ }
+ if (*respline != '1')
+ {
+ xfree (respline);
+ return FTPRERR;
+ }
+ xfree (respline);
+ /* All OK. */
+ return FTPOK;
+}
+
+/* Sends the LIST command to the server. If FILE is NULL, send just
+ `LIST' (no space). */
+uerr_t
+ftp_list (int csock, const char *file, bool avoid_list_a, bool avoid_list,
+ bool *list_a_used)
+{
+ char *request, *respline;
+ int nwritten;
+ uerr_t err;
+ bool ok = false;
+ size_t i = 0;
+
+ /* 2013-10-12 Andrea Urbani (matfanjol)
+ For more information about LIST and "LIST -a" please look at ftp.c,
+ function getftp, text "__LIST_A_EXPLANATION__".
+
+ If somebody changes the following commands, please, checks also the
+ later "i" variable. */
+ static const char *list_commands[] = {
+ "LIST -a",
+ "LIST"
+ };
+
+ *list_a_used = false;
+
+ if (avoid_list_a)
+ {
+ i = countof (list_commands)- 1;
+ DEBUGP (("(skipping \"LIST -a\")"));
+ }
+
+
+ do {
+ /* Send request. */
+ request = ftp_request (list_commands[i], file);
+ nwritten = fd_write (csock, request, strlen (request), -1);
+ if (nwritten < 0)
+ {
+ xfree (request);
+ return WRITEFAILED;
+ }
+ xfree (request);
+ /* Get appropriate response. */
+ err = ftp_response (csock, &respline);
+ if (err == FTPOK)
+ {
+ if (*respline == '5')
+ {
+ err = FTPNSFOD;
+ }
+ else if (*respline == '1')
+ {
+ err = FTPOK;
+ ok = true;
+ /* Which list command was used? */
+ *list_a_used = (i == 0);
+ }
+ else
+ {
+ err = FTPRERR;
+ }
+ xfree (respline);
+ }
+ ++i;
+ if ((avoid_list) && (i == 1))
+ {
+ /* I skip LIST */
+ ++i;
+ DEBUGP (("(skipping \"LIST\")"));
+ }
+ } while (i < countof (list_commands) && !ok);
+
+ return err;
+}
+
+/* Sends the SYST command to the server. */
+uerr_t
+ftp_syst (int csock, enum stype *server_type, enum ustype *unix_type)
+{
+ char *request, *respline;
+ int nwritten;
+ uerr_t err;
+ char *ftp_last_respline;
+
+ /* Send SYST request. */
+ request = ftp_request ("SYST", NULL);
+ nwritten = fd_write (csock, request, strlen (request), -1);
+ if (nwritten < 0)
+ {
+ xfree (request);
+ return WRITEFAILED;
+ }
+ xfree (request);
+
+ /* Get appropriate response. */
+ err = ftp_response (csock, &respline);
+ if (err != FTPOK)
+ return err;
+ if (*respline == '5')
+ {
+ xfree (respline);
+ return FTPSRVERR;
+ }
+
+ ftp_last_respline = strdup (respline);
+
+ /* Skip the number (215, but 200 (!!!) in case of VMS) */
+ strtok (respline, " ");
+
+ /* Which system type has been reported (we are interested just in the
+ first word of the server response)? */
+ request = strtok (NULL, " ");
+
+ *unix_type = UST_OTHER;
+
+ if (request == NULL)
+ *server_type = ST_OTHER;
+ else if (!c_strcasecmp (request, "VMS"))
+ *server_type = ST_VMS;
+ else if (!c_strcasecmp (request, "UNIX"))
+ {
+ *server_type = ST_UNIX;
+ /* 2013-10-17 Andrea Urbani (matfanjol)
+ I check more in depth the system type */
+ if (!c_strncasecmp (ftp_last_respline, "215 UNIX Type: L8", 17))
+ *unix_type = UST_TYPE_L8;
+ else if (!c_strncasecmp (ftp_last_respline,
+ "215 UNIX MultiNet Unix Emulation V5.3(93)", 41))
+ *unix_type = UST_MULTINET;
+ }
+ else if (!c_strcasecmp (request, "WINDOWS_NT")
+ || !c_strcasecmp (request, "WINDOWS2000"))
+ *server_type = ST_WINNT;
+ else if (!c_strcasecmp (request, "MACOS"))
+ *server_type = ST_MACOS;
+ else if (!c_strcasecmp (request, "OS/400"))
+ *server_type = ST_OS400;
+ else
+ *server_type = ST_OTHER;
+
+ xfree (ftp_last_respline);
+ xfree (respline);
+ /* All OK. */
+ return FTPOK;
+}
+
+/* Sends the PWD command to the server. */
+uerr_t
+ftp_pwd (int csock, char **pwd)
+{
+ char *request, *respline;
+ int nwritten;
+ uerr_t err;
+
+ /* Send PWD request. */
+ request = ftp_request ("PWD", NULL);
+ nwritten = fd_write (csock, request, strlen (request), -1);
+ if (nwritten < 0)
+ {
+ xfree (request);
+ return WRITEFAILED;
+ }
+ xfree (request);
+ /* Get appropriate response. */
+ err = ftp_response (csock, &respline);
+ if (err != FTPOK)
+ return err;
+ if (*respline == '5')
+ {
+ err:
+ xfree (respline);
+ return FTPSRVERR;
+ }
+
+ /* Skip the number (257), leading citation mark, trailing citation mark
+ and everything following it. */
+ strtok (respline, "\"");
+ request = strtok (NULL, "\"");
+ if (!request)
+ /* Treat the malformed response as an error, which the caller has
+ to handle gracefully anyway. */
+ goto err;
+
+ /* Has the `pwd' been already allocated? Free! */
+ xfree (*pwd);
+
+ *pwd = xstrdup (request);
+
+ xfree (respline);
+ /* All OK. */
+ return FTPOK;
+}
+
+/* Sends the SIZE command to the server, and returns the value in 'size'.
+ * If an error occurs, size is set to zero. */
+uerr_t
+ftp_size (int csock, const char *file, wgint *size)
+{
+ char *request, *respline;
+ int nwritten;
+ uerr_t err;
+
+ /* Send PWD request. */
+ request = ftp_request ("SIZE", file);
+ nwritten = fd_write (csock, request, strlen (request), -1);
+ if (nwritten < 0)
+ {
+ xfree (request);
+ *size = 0;
+ return WRITEFAILED;
+ }
+ xfree (request);
+ /* Get appropriate response. */
+ err = ftp_response (csock, &respline);
+ if (err != FTPOK)
+ {
+ *size = 0;
+ return err;
+ }
+ if (*respline == '5')
+ {
+ /*
+ * Probably means SIZE isn't supported on this server.
+ * Error is nonfatal since SIZE isn't in RFC 959
+ */
+ xfree (respline);
+ *size = 0;
+ return FTPOK;
+ }
+
+ errno = 0;
+ *size = str_to_wgint (respline + 4, NULL, 10);
+ if (errno)
+ {
+ /*
+ * Couldn't parse the response for some reason. On the (few)
+ * tests I've done, the response is 213 <SIZE> with nothing else -
+ * maybe something a bit more resilient is necessary. It's not a
+ * fatal error, however.
+ */
+ xfree (respline);
+ *size = 0;
+ return FTPOK;
+ }
+
+ xfree (respline);
+ /* All OK. */
+ return FTPOK;
+}
+
+/* If URL's params are of the form "type=X", return character X.
+ Otherwise, return 'I' (the default type). */
+char
+ftp_process_type (const char *params)
+{
+ if (params
+ && 0 == strncasecmp (params, "type=", 5)
+ && params[5] != '\0')
+ return c_toupper (params[5]);
+ else
+ return 'I';
+}