diff options
Diffstat (limited to '')
-rw-r--r-- | src/ftp.c | 2844 |
1 files changed, 2844 insertions, 0 deletions
diff --git a/src/ftp.c b/src/ftp.c new file mode 100644 index 0000000..6b8bdf8 --- /dev/null +++ b/src/ftp.c @@ -0,0 +1,2844 @@ +/* File Transfer Protocol support. + Copyright (C) 1996-2011, 2014-2015, 2018 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 <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <strings.h> +#include <unistd.h> +#include <assert.h> +#include <errno.h> +#include <time.h> + +#include "utils.h" +#include "url.h" +#include "retr.h" +#include "ftp.h" +#include "ssl.h" +#include "connect.h" +#include "host.h" +#include "netrc.h" +#include "convert.h" /* for downloaded_file */ +#include "recur.h" /* for INFINITE_RECURSION */ +#include "warc.h" +#include "c-strcase.h" +#ifdef ENABLE_XATTR +#include "xattr.h" +#endif + +#ifdef __VMS +# include "vms.h" +#endif /* def __VMS */ + + +/* File where the "ls -al" listing will be saved. */ +#ifdef MSDOS +#define LIST_FILENAME "_listing" +#else +#define LIST_FILENAME ".listing" +#endif + +typedef struct +{ + int st; /* connection status */ + int cmd; /* command code */ + int csock; /* control connection socket */ + double dltime; /* time of the download in msecs */ + enum stype rs; /* remote system reported by ftp server */ + enum ustype rsu; /* when rs is ST_UNIX, here there are more details */ + char *id; /* initial directory */ + char *target; /* target file name */ + struct url *proxy; /* FTWK-style proxy */ +} ccon; + + +/* Look for regexp "( *[0-9]+ *byte" (literal parenthesis) anywhere in + the string S, and return the number converted to wgint, if found, 0 + otherwise. */ +static wgint +ftp_expected_bytes (const char *s) +{ + wgint res; + + while (1) + { + while (*s && *s != '(') + ++s; + if (!*s) + return 0; + ++s; /* skip the '(' */ + res = str_to_wgint (s, (char **) &s, 10); + if (!*s) + return 0; + while (*s && c_isspace (*s)) + ++s; + if (!*s) + return 0; + if (c_tolower (*s) != 'b') + continue; + if (c_strncasecmp (s, "byte", 4)) + continue; + else + break; + } + return res; +} + +#ifdef ENABLE_IPV6 +/* + * This function sets up a passive data connection with the FTP server. + * It is merely a wrapper around ftp_epsv, ftp_lpsv and ftp_pasv. + */ +static uerr_t +ftp_do_pasv (int csock, ip_address *addr, int *port) +{ + uerr_t err; + + /* We need to determine the address family and need to call + getpeername, so while we're at it, store the address to ADDR. + ftp_pasv and ftp_lpsv can simply override it. */ + if (!socket_ip_address (csock, addr, ENDPOINT_PEER)) + abort (); + + /* If our control connection is over IPv6, then we first try EPSV and then + * LPSV if the former is not supported. If the control connection is over + * IPv4, we simply issue the good old PASV request. */ + switch (addr->family) + { + case AF_INET: + if (!opt.server_response) + logputs (LOG_VERBOSE, "==> PASV ... "); + err = ftp_pasv (csock, addr, port); + break; + case AF_INET6: + if (!opt.server_response) + logputs (LOG_VERBOSE, "==> EPSV ... "); + err = ftp_epsv (csock, addr, port); + + /* If EPSV is not supported try LPSV */ + if (err == FTPNOPASV) + { + if (!opt.server_response) + logputs (LOG_VERBOSE, "==> LPSV ... "); + err = ftp_lpsv (csock, addr, port); + } + break; + default: + abort (); + } + + return err; +} + +/* + * This function sets up an active data connection with the FTP server. + * It is merely a wrapper around ftp_eprt, ftp_lprt and ftp_port. + */ +static uerr_t +ftp_do_port (int csock, int *local_sock) +{ + uerr_t err; + ip_address cip; + + if (!socket_ip_address (csock, &cip, ENDPOINT_PEER)) + abort (); + + /* If our control connection is over IPv6, then we first try EPRT and then + * LPRT if the former is not supported. If the control connection is over + * IPv4, we simply issue the good old PORT request. */ + switch (cip.family) + { + case AF_INET: + if (!opt.server_response) + logputs (LOG_VERBOSE, "==> PORT ... "); + err = ftp_port (csock, local_sock); + break; + case AF_INET6: + if (!opt.server_response) + logputs (LOG_VERBOSE, "==> EPRT ... "); + err = ftp_eprt (csock, local_sock); + + /* If EPRT is not supported try LPRT */ + if (err == FTPPORTERR) + { + if (!opt.server_response) + logputs (LOG_VERBOSE, "==> LPRT ... "); + err = ftp_lprt (csock, local_sock); + } + break; + default: + abort (); + } + return err; +} +#else + +static uerr_t +ftp_do_pasv (int csock, ip_address *addr, int *port) +{ + if (!opt.server_response) + logputs (LOG_VERBOSE, "==> PASV ... "); + return ftp_pasv (csock, addr, port); +} + +static uerr_t +ftp_do_port (int csock, int *local_sock) +{ + if (!opt.server_response) + logputs (LOG_VERBOSE, "==> PORT ... "); + return ftp_port (csock, local_sock); +} +#endif + +static void +print_length (wgint size, wgint start, bool authoritative) +{ + logprintf (LOG_VERBOSE, _("Length: %s"), number_to_static_string (size)); + if (size >= 1024) + logprintf (LOG_VERBOSE, " (%s)", human_readable (size, 10, 1)); + if (start > 0) + { + if (size - start >= 1024) + logprintf (LOG_VERBOSE, _(", %s (%s) remaining"), + number_to_static_string (size - start), + human_readable (size - start, 10, 1)); + else + logprintf (LOG_VERBOSE, _(", %s remaining"), + number_to_static_string (size - start)); + } + logputs (LOG_VERBOSE, !authoritative ? _(" (unauthoritative)\n") : "\n"); +} + +static uerr_t ftp_get_listing (struct url *, struct url *, ccon *, struct fileinfo **); + +static uerr_t +get_ftp_greeting (int csock, ccon *con) +{ + uerr_t err = 0; + + /* Get the server's greeting */ + err = ftp_greeting (csock); + if (err != FTPOK) + { + logputs (LOG_NOTQUIET, "Error in server response. Closing.\n"); + fd_close (csock); + con->csock = -1; + } + + return err; +} + +#ifdef HAVE_SSL +static uerr_t +init_control_ssl_connection (int csock, struct url *u, bool *using_control_security) +{ + bool using_security = false; + + /* If '--ftps-implicit' was passed, perform the SSL handshake directly, + * and do not send an AUTH command. + * Otherwise send an AUTH sequence before login, + * and perform the SSL handshake if accepted by server. + */ + if (!opt.ftps_implicit && !opt.server_response) + logputs (LOG_VERBOSE, "==> AUTH TLS ... "); + if (opt.ftps_implicit || ftp_auth (csock, SCHEME_FTPS) == FTPOK) + { + if (!ssl_connect_wget (csock, u->host, NULL)) + { + fd_close (csock); + return CONSSLERR; + } + else if (!ssl_check_certificate (csock, u->host)) + { + fd_close (csock); + return VERIFCERTERR; + } + + if (!opt.ftps_implicit && !opt.server_response) + logputs (LOG_VERBOSE, " done.\n"); + + /* If implicit FTPS was requested, we act as "normal" FTP, but over SSL. + * We're not using RFC 2228 commands. + */ + using_security = true; + } + else + { + /* The server does not support 'AUTH TLS'. + * Check if --ftps-fallback-to-ftp was passed. */ + if (opt.ftps_fallback_to_ftp) + { + logputs (LOG_NOTQUIET, "Server does not support AUTH TLS. Falling back to FTP.\n"); + using_security = false; + } + else + { + fd_close (csock); + return FTPNOAUTH; + } + } + + *using_control_security = using_security; + return NOCONERROR; +} +#endif + +/* Retrieves a file with denoted parameters through opening an FTP + connection to the server. It always closes the data connection, + and closes the control connection in case of error. If warc_tmp + is non-NULL, the downloaded data will be written there as well. */ +static uerr_t +getftp (struct url *u, struct url *original_url, + wgint passed_expected_bytes, wgint *qtyread, + wgint restval, ccon *con, int count, wgint *last_expected_bytes, + FILE *warc_tmp) +{ + int csock, dtsock, local_sock, res; + uerr_t err = RETROK; /* appease the compiler */ + FILE *fp = NULL; + char *respline, *tms; + const char *user, *passwd, *tmrate; + int cmd = con->cmd; + wgint expected_bytes = 0; + bool got_expected_bytes = false; + bool rest_failed = false; + int flags; + wgint rd_size, previous_rd_size = 0; + char type_char; + bool try_again; + bool list_a_used = false; +#ifdef HAVE_SSL + enum prot_level prot = (opt.ftps_clear_data_connection ? PROT_CLEAR : PROT_PRIVATE); + /* these variables tell whether the target server + * accepts the security extensions (RFC 2228) or not, + * and whether we're actually using any of them + * (encryption at the control connection only, + * or both at control and data connections) */ + bool using_control_security = false, using_data_security = false; +#endif + + assert (con != NULL); + assert (con->target != NULL); + + /* Debug-check of the sanity of the request by making sure that LIST + and RETR are never both requested (since we can handle only one + at a time. */ + assert (!((cmd & DO_LIST) && (cmd & DO_RETR))); + /* Make sure that at least *something* is requested. */ + assert ((cmd & (DO_LIST | DO_CWD | DO_RETR | DO_LOGIN)) != 0); + + *qtyread = restval; + + /* Find the username with priority */ + if (u->user) + user = u->user; + else if (opt.user && (opt.use_askpass || opt.ask_passwd)) + user = opt.user; + else if (opt.ftp_user) + user = opt.ftp_user; + else if (opt.user) + user = opt.user; + else + user = NULL; + + /* Find the password with priority */ + if (u->passwd) + passwd = u->passwd; + else if (opt.passwd && (opt.use_askpass || opt.ask_passwd)) + passwd = opt.passwd; + else if (opt.ftp_passwd) + passwd = opt.ftp_passwd; + else if (opt.passwd) + passwd = opt.passwd; + else + passwd = NULL; + + /* Check for ~/.netrc if none of the above match */ + if (opt.netrc && (!user || !passwd)) + search_netrc (u->host, (const char **) &user, (const char **) &passwd, 1, NULL); + + if (!user) user = "anonymous"; + if (!passwd) passwd = "-wget@"; + + dtsock = -1; + local_sock = -1; + con->dltime = 0; + +#ifdef HAVE_SSL + if (u->scheme == SCHEME_FTPS) + { + /* Initialize SSL layer first */ + if (!ssl_init ()) + { + scheme_disable (SCHEME_FTPS); + logprintf (LOG_NOTQUIET, _("Could not initialize SSL. It will be disabled.")); + err = SSLINITFAILED; + return err; + } + + /* If we're using the default FTP port and implicit FTPS was requested, + * rewrite the port to the default *implicit* FTPS port. + */ + if (opt.ftps_implicit && u->port == DEFAULT_FTP_PORT) + { + DEBUGP (("Implicit FTPS was specified. Rewriting default port to %d.\n", DEFAULT_FTPS_IMPLICIT_PORT)); + u->port = DEFAULT_FTPS_IMPLICIT_PORT; + } + } +#endif + + if (!(cmd & DO_LOGIN)) + { + csock = con->csock; +#ifdef HAVE_SSL + using_data_security = con->st & DATA_CHANNEL_SECURITY; +#endif + } + else /* cmd & DO_LOGIN */ + { + char *host = con->proxy ? con->proxy->host : u->host; + int port = con->proxy ? con->proxy->port : u->port; + + /* Login to the server: */ + + /* First: Establish the control connection. */ + + csock = connect_to_host (host, port); + if (csock == E_HOST) + return HOSTERR; + else if (csock < 0) + return (retryable_socket_connect_error (errno) + ? CONERROR : CONIMPOSSIBLE); + + if (cmd & LEAVE_PENDING) + con->csock = csock; + else + con->csock = -1; + +#ifdef HAVE_SSL + if (u->scheme == SCHEME_FTPS) + { + /* If we're in implicit FTPS mode, we have to set up SSL/TLS before everything else. + * Otherwise we first read the server's greeting, and then send an "AUTH TLS". + */ + if (opt.ftps_implicit) + { + err = init_control_ssl_connection (csock, u, &using_control_security); + if (err != NOCONERROR) + return err; + err = get_ftp_greeting (csock, con); + if (err != FTPOK) + return err; + } + else + { + err = get_ftp_greeting (csock, con); + if (err != FTPOK) + return err; + err = init_control_ssl_connection (csock, u, &using_control_security); + if (err != NOCONERROR) + return err; + } + } + else + { + err = get_ftp_greeting (csock, con); + if (err != FTPOK) + return err; + } +#else + err = get_ftp_greeting (csock, con); + if (err != FTPOK) + return err; +#endif + + /* Second: Login with proper USER/PASS sequence. */ + logprintf (LOG_VERBOSE, _("Logging in as %s ... "), + quotearg_style (escape_quoting_style, user)); + if (opt.server_response) + logputs (LOG_ALWAYS, "\n"); + if (con->proxy) + { + /* If proxy is in use, log in as username@target-site. */ + char *logname = concat_strings (user, "@", u->host, (char *) 0); + err = ftp_login (csock, logname, passwd); + xfree (logname); + } + else + err = ftp_login (csock, user, passwd); + + /* FTPRERR, FTPSRVERR, WRITEFAILED, FTPLOGREFUSED, FTPLOGINC */ + switch (err) + { + case FTPRERR: + logputs (LOG_VERBOSE, "\n"); + logputs (LOG_NOTQUIET, _("\ +Error in server response, closing control connection.\n")); + fd_close (csock); + con->csock = -1; + return err; + case FTPSRVERR: + logputs (LOG_VERBOSE, "\n"); + logputs (LOG_NOTQUIET, _("Error in server greeting.\n")); + fd_close (csock); + con->csock = -1; + return err; + case WRITEFAILED: + logputs (LOG_VERBOSE, "\n"); + logputs (LOG_NOTQUIET, + _("Write failed, closing control connection.\n")); + fd_close (csock); + con->csock = -1; + return err; + case FTPLOGREFUSED: + logputs (LOG_VERBOSE, "\n"); + logputs (LOG_NOTQUIET, _("The server refuses login.\n")); + fd_close (csock); + con->csock = -1; + return FTPLOGREFUSED; + case FTPLOGINC: + logputs (LOG_VERBOSE, "\n"); + logputs (LOG_NOTQUIET, _("Login incorrect.\n")); + fd_close (csock); + con->csock = -1; + return FTPLOGINC; + case FTPOK: + if (!opt.server_response) + logputs (LOG_VERBOSE, _("Logged in!\n")); + break; + default: + abort (); + } + +#ifdef HAVE_SSL + if (using_control_security) + { + /* Send the PBSZ and PROT commands, in that order. + * If we are here it means that the server has already accepted + * some form of FTPS. Thus, these commands must work. + * If they don't work, that's an error. There's no sense in honoring + * --ftps-fallback-to-ftp or similar options. */ + if (u->scheme == SCHEME_FTPS) + { + if (!opt.server_response) + logputs (LOG_VERBOSE, "==> PBSZ 0 ... "); + if ((err = ftp_pbsz (csock, 0)) == FTPNOPBSZ) + { + logputs (LOG_NOTQUIET, _("Server did not accept the 'PBSZ 0' command.\n")); + return err; + } + if (!opt.server_response) + logputs (LOG_VERBOSE, "done."); + + if (!opt.server_response) + logprintf (LOG_VERBOSE, " ==> PROT %c ... ", (int) prot); + if ((err = ftp_prot (csock, prot)) == FTPNOPROT) + { + logprintf (LOG_NOTQUIET, _("Server did not accept the 'PROT %c' command.\n"), (int) prot); + return err; + } + if (!opt.server_response) + logputs (LOG_VERBOSE, "done.\n"); + + if (prot != PROT_CLEAR) + { + using_data_security = true; + con->st |= DATA_CHANNEL_SECURITY; + } + } + } +#endif + + /* Third: Get the system type */ + if (!opt.server_response) + logprintf (LOG_VERBOSE, "==> SYST ... "); + err = ftp_syst (csock, &con->rs, &con->rsu); + /* FTPRERR */ + switch (err) + { + case FTPRERR: + logputs (LOG_VERBOSE, "\n"); + logputs (LOG_NOTQUIET, _("\ +Error in server response, closing control connection.\n")); + fd_close (csock); + con->csock = -1; + return err; + case FTPSRVERR: + logputs (LOG_VERBOSE, "\n"); + logputs (LOG_NOTQUIET, + _("Server error, can't determine system type.\n")); + break; + case FTPOK: + /* Everything is OK. */ + break; + default: + abort (); + } + if (!opt.server_response && err != FTPSRVERR) + logputs (LOG_VERBOSE, _("done. ")); + + /* 2013-10-17 Andrea Urbani (matfanjol) + According to the system type I choose which + list command will be used. + If I don't know that system, I will try, the + first time of each session, "LIST -a" and + "LIST". (see __LIST_A_EXPLANATION__ below) */ + switch (con->rs) + { + case ST_VMS: + /* About ST_VMS there is an old note: + 2008-01-29 SMS. For a VMS FTP server, where "LIST -a" may not + fail, but will never do what is desired here, + skip directly to the simple "LIST" command + (assumed to be the last one in the list). */ + DEBUGP (("\nVMS: I know it and I will use \"LIST\" as standard list command\n")); + con->st |= LIST_AFTER_LIST_A_CHECK_DONE; + con->st |= AVOID_LIST_A; + break; + case ST_UNIX: + if (con->rsu == UST_MULTINET) + { + DEBUGP (("\nUNIX MultiNet: I know it and I will use \"LIST\" " + "as standard list command\n")); + con->st |= LIST_AFTER_LIST_A_CHECK_DONE; + con->st |= AVOID_LIST_A; + } + else if (con->rsu == UST_TYPE_L8) + { + DEBUGP (("\nUNIX TYPE L8: I know it and I will use \"LIST -a\" " + "as standard list command\n")); + con->st |= LIST_AFTER_LIST_A_CHECK_DONE; + con->st |= AVOID_LIST; + } + break; + default: + break; + } + + /* Fourth: Find the initial ftp directory */ + + if (!opt.server_response) + logprintf (LOG_VERBOSE, "==> PWD ... "); + err = ftp_pwd (csock, &con->id); + /* FTPRERR */ + switch (err) + { + case FTPRERR: + logputs (LOG_VERBOSE, "\n"); + logputs (LOG_NOTQUIET, _("\ +Error in server response, closing control connection.\n")); + fd_close (csock); + con->csock = -1; + return err; + case FTPSRVERR : + /* PWD unsupported -- assume "/". */ + xfree (con->id); + con->id = xstrdup ("/"); + break; + case FTPOK: + /* Everything is OK. */ + break; + default: + abort (); + } + +#if 0 + /* 2004-09-17 SMS. + Don't help me out. Please. + A reasonably recent VMS FTP server will cope just fine with + UNIX file specifications. This code just spoils things. + Discarding the device name, for example, is not a wise move. + This code was disabled but left in as an example of what not + to do. + */ + + /* VMS will report something like "PUB$DEVICE:[INITIAL.FOLDER]". + Convert it to "/INITIAL/FOLDER" */ + if (con->rs == ST_VMS) + { + char *path = strchr (con->id, '['); + char *pathend = path ? strchr (path + 1, ']') : NULL; + if (!path || !pathend) + DEBUGP (("Initial VMS directory not in the form [...]!\n")); + else + { + char *idir = con->id; + DEBUGP (("Preprocessing the initial VMS directory\n")); + DEBUGP ((" old = '%s'\n", con->id)); + /* We do the conversion in-place by copying the stuff + between [ and ] to the beginning, and changing dots + to slashes at the same time. */ + *idir++ = '/'; + for (++path; path < pathend; path++, idir++) + *idir = *path == '.' ? '/' : *path; + *idir = '\0'; + DEBUGP ((" new = '%s'\n\n", con->id)); + } + } +#endif /* 0 */ + + if (!opt.server_response) + logputs (LOG_VERBOSE, _("done.\n")); + + /* Fifth: Set the FTP type. */ + type_char = ftp_process_type (u->params); + if (!opt.server_response) + logprintf (LOG_VERBOSE, "==> TYPE %c ... ", type_char); + err = ftp_type (csock, type_char); + /* FTPRERR, WRITEFAILED, FTPUNKNOWNTYPE */ + switch (err) + { + case FTPRERR: + logputs (LOG_VERBOSE, "\n"); + logputs (LOG_NOTQUIET, _("\ +Error in server response, closing control connection.\n")); + fd_close (csock); + con->csock = -1; + return err; + case WRITEFAILED: + logputs (LOG_VERBOSE, "\n"); + logputs (LOG_NOTQUIET, + _("Write failed, closing control connection.\n")); + fd_close (csock); + con->csock = -1; + return err; + case FTPUNKNOWNTYPE: + logputs (LOG_VERBOSE, "\n"); + logprintf (LOG_NOTQUIET, + _("Unknown type `%c', closing control connection.\n"), + type_char); + fd_close (csock); + con->csock = -1; + return err; + case FTPOK: + /* Everything is OK. */ + break; + default: + abort (); + } + if (!opt.server_response) + logputs (LOG_VERBOSE, _("done. ")); + } /* do login */ + + if (cmd & DO_CWD) + { + if (!*u->dir) + logputs (LOG_VERBOSE, _("==> CWD not needed.\n")); + else + { + const char *targ = NULL; + int cwd_count; + int cwd_end; + int cwd_start; + + char *target = u->dir; + + DEBUGP (("changing working directory\n")); + + /* Change working directory. To change to a non-absolute + Unix directory, we need to prepend initial directory + (con->id) to it. Absolute directories "just work". + + A relative directory is one that does not begin with '/' + and, on non-Unix OS'es, one that doesn't begin with + "[a-z]:". + + This is not done for OS400, which doesn't use + "/"-delimited directories, nor does it support directory + hierarchies. "CWD foo" followed by "CWD bar" leaves us + in "bar", not in "foo/bar", as would be customary + elsewhere. */ + + /* 2004-09-20 SMS. + Why is this wise even on UNIX? It certainly fouls VMS. + See below for a more reliable, more universal method. + */ + + /* 2008-04-22 MJC. + I'm not crazy about it either. I'm informed it's useful + for misconfigured servers that have some dirs in the path + with +x but -r, but this method is not RFC-conformant. I + understand the need to deal with crappy server + configurations, but it's far better to use the canonical + method first, and fall back to kludges second. + */ + + if (target[0] != '/' + && !(con->rs != ST_UNIX + && c_isalpha (target[0]) + && target[1] == ':') + && (con->rs != ST_OS400) + && (con->rs != ST_VMS)) + { + int idlen = strlen (con->id); + char *ntarget, *p; + + /* Strip trailing slash(es) from con->id. */ + while (idlen > 0 && con->id[idlen - 1] == '/') + --idlen; + p = ntarget = (char *)alloca (idlen + 1 + strlen (u->dir) + 1); + memcpy (p, con->id, idlen); + p += idlen; + *p++ = '/'; + strcpy (p, target); + + DEBUGP (("Prepended initial PWD to relative path:\n")); + DEBUGP ((" pwd: '%s'\n old: '%s'\n new: '%s'\n", + con->id, target, ntarget)); + target = ntarget; + } + +#if 0 + /* 2004-09-17 SMS. + Don't help me out. Please. + A reasonably recent VMS FTP server will cope just fine with + UNIX file specifications. This code just spoils things. + Discarding the device name, for example, is not a wise + move. + This code was disabled but left in as an example of what + not to do. + */ + + /* If the FTP host runs VMS, we will have to convert the absolute + directory path in UNIX notation to absolute directory path in + VMS notation as VMS FTP servers do not like UNIX notation of + absolute paths. "VMS notation" is [dir.subdir.subsubdir]. */ + + if (con->rs == ST_VMS) + { + char *tmpp; + char *ntarget = (char *)alloca (strlen (target) + 2); + /* We use a converted initial dir, so directories in + TARGET will be separated with slashes, something like + "/INITIAL/FOLDER/DIR/SUBDIR". Convert that to + "[INITIAL.FOLDER.DIR.SUBDIR]". */ + strcpy (ntarget, target); + assert (*ntarget == '/'); + *ntarget = '['; + for (tmpp = ntarget + 1; *tmpp; tmpp++) + if (*tmpp == '/') + *tmpp = '.'; + *tmpp++ = ']'; + *tmpp = '\0'; + DEBUGP (("Changed file name to VMS syntax:\n")); + DEBUGP ((" Unix: '%s'\n VMS: '%s'\n", target, ntarget)); + target = ntarget; + } +#endif /* 0 */ + + /* 2004-09-20 SMS. + A relative directory is relative to the initial directory. + Thus, what _is_ useful on VMS (and probably elsewhere) is + to CWD to the initial directory (ideally, whatever the + server reports, _exactly_, NOT badly UNIX-ixed), and then + CWD to the (new) relative directory. This should probably + be restructured as a function, called once or twice, but + I'm lazy enough to take the badly indented loop short-cut + for now. + */ + + /* Decide on one pass (absolute) or two (relative). + The VMS restriction may be relaxed when the squirrely code + above is reformed. + */ + if ((con->rs == ST_VMS) && (target[0] != '/')) + { + cwd_start = 0; + DEBUGP (("Using two-step CWD for relative path.\n")); + } + else + { + /* Go straight to the target. */ + cwd_start = 1; + } + + /* At least one VMS FTP server (TCPware V5.6-2) can switch to + a UNIX emulation mode when given a UNIX-like directory + specification (like "a/b/c"). If allowed to continue this + way, LIST interpretation will be confused, because the + system type (SYST response) will not be re-checked, and + future UNIX-format directory listings (for multiple URLs or + "-r") will be horribly misinterpreted. + + The cheap and nasty work-around is to do a "CWD []" after a + UNIX-like directory specification is used. (A single-level + directory is harmless.) This puts the TCPware server back + into VMS mode, and does no harm on other servers. + + Unlike the rest of this block, this particular behavior + _is_ VMS-specific, so it gets its own VMS test. + */ + if ((con->rs == ST_VMS) && (strchr (target, '/') != NULL)) + { + cwd_end = 3; + DEBUGP (("Using extra \"CWD []\" step for VMS server.\n")); + } + else + { + cwd_end = 2; + } + + /* 2004-09-20 SMS. */ + /* Sorry about the deviant indenting. Laziness. */ + + for (cwd_count = cwd_start; cwd_count < cwd_end; cwd_count++) + { + switch (cwd_count) + { + case 0: + /* Step one (optional): Go to the initial directory, + exactly as reported by the server. + */ + targ = con->id; + break; + + case 1: + /* Step two: Go to the target directory. (Absolute or + relative will work now.) + */ + targ = target; + break; + + case 2: + /* Step three (optional): "CWD []" to restore server + VMS-ness. + */ + targ = "[]"; + break; + + default: + logprintf (LOG_ALWAYS, _("Logically impossible section reached in getftp()")); + logprintf (LOG_ALWAYS, _("cwd_count: %d\ncwd_start: %d\ncwd_end: %d\n"), + cwd_count, cwd_start, cwd_end); + abort (); + } + + if (!opt.server_response) + logprintf (LOG_VERBOSE, "==> CWD (%d) %s ... ", cwd_count, + quotearg_style (escape_quoting_style, target)); + + err = ftp_cwd (csock, targ); + + /* FTPRERR, WRITEFAILED, FTPNSFOD */ + switch (err) + { + case FTPRERR: + logputs (LOG_VERBOSE, "\n"); + logputs (LOG_NOTQUIET, _("\ +Error in server response, closing control connection.\n")); + fd_close (csock); + con->csock = -1; + return err; + case WRITEFAILED: + logputs (LOG_VERBOSE, "\n"); + logputs (LOG_NOTQUIET, + _("Write failed, closing control connection.\n")); + fd_close (csock); + con->csock = -1; + return err; + case FTPNSFOD: + logputs (LOG_VERBOSE, "\n"); + logprintf (LOG_NOTQUIET, _("No such directory %s.\n\n"), + quote (u->dir)); + fd_close (csock); + con->csock = -1; + return err; + case FTPOK: + break; + default: + abort (); + } + + if (!opt.server_response) + logputs (LOG_VERBOSE, _("done.\n")); + + } /* for */ + + /* 2004-09-20 SMS. */ + + } /* else */ + } + else /* do not CWD */ + logputs (LOG_VERBOSE, _("==> CWD not required.\n")); + + if ((cmd & DO_RETR) && passed_expected_bytes == 0) + { + if (opt.verbose) + { + if (!opt.server_response) + logprintf (LOG_VERBOSE, "==> SIZE %s ... ", + quotearg_style (escape_quoting_style, u->file)); + } + + err = ftp_size (csock, u->file, &expected_bytes); + /* FTPRERR */ + switch (err) + { + case FTPRERR: + case FTPSRVERR: + logputs (LOG_VERBOSE, "\n"); + logputs (LOG_NOTQUIET, _("\ +Error in server response, closing control connection.\n")); + fd_close (csock); + con->csock = -1; + return err; + case FTPOK: + got_expected_bytes = true; + /* Everything is OK. */ + break; + default: + abort (); + } + if (!opt.server_response) + { + logprintf (LOG_VERBOSE, "%s\n", + expected_bytes ? + number_to_static_string (expected_bytes) : + _("done.\n")); + } + } + + if (cmd & DO_RETR && restval > 0 && restval == expected_bytes) + { + /* Server confirms that file has length restval. We should stop now. + Some servers (f.e. NcFTPd) return error when receive REST 0 */ + logputs (LOG_VERBOSE, _("File has already been retrieved.\n")); + fd_close (csock); + con->csock = -1; + return RETRFINISHED; + } + + do + { + try_again = false; + /* If anything is to be retrieved, PORT (or PASV) must be sent. */ + if (cmd & (DO_LIST | DO_RETR)) + { + if (opt.ftp_pasv) + { + ip_address passive_addr; + int passive_port; + err = ftp_do_pasv (csock, &passive_addr, &passive_port); + /* FTPRERR, WRITEFAILED, FTPNOPASV, FTPINVPASV */ + switch (err) + { + case FTPRERR: + logputs (LOG_VERBOSE, "\n"); + logputs (LOG_NOTQUIET, _("\ +Error in server response, closing control connection.\n")); + fd_close (csock); + con->csock = -1; + return err; + case WRITEFAILED: + logputs (LOG_VERBOSE, "\n"); + logputs (LOG_NOTQUIET, + _("Write failed, closing control connection.\n")); + fd_close (csock); + con->csock = -1; + return err; + case FTPNOPASV: + logputs (LOG_VERBOSE, "\n"); + logputs (LOG_NOTQUIET, _("Cannot initiate PASV transfer.\n")); + break; + case FTPINVPASV: + logputs (LOG_VERBOSE, "\n"); + logputs (LOG_NOTQUIET, _("Cannot parse PASV response.\n")); + break; + case FTPOK: + break; + default: + abort (); + } /* switch (err) */ + if (err==FTPOK) + { + DEBUGP (("trying to connect to %s port %d\n", + print_address (&passive_addr), passive_port)); + dtsock = connect_to_ip (&passive_addr, passive_port, NULL); + if (dtsock < 0) + { + int save_errno = errno; + fd_close (csock); + con->csock = -1; + logprintf (LOG_VERBOSE, _("couldn't connect to %s port %d: %s\n"), + print_address (&passive_addr), passive_port, + strerror (save_errno)); + return (retryable_socket_connect_error (save_errno) + ? CONERROR : CONIMPOSSIBLE); + } + + if (!opt.server_response) + logputs (LOG_VERBOSE, _("done. ")); + } + else + return err; + + /* + * We do not want to fall back from PASSIVE mode to ACTIVE mode ! + * The reason is the PORT command exposes the client's real IP address + * to the server. Bad for someone who relies on privacy via a ftp proxy. + */ + } + else + { + err = ftp_do_port (csock, &local_sock); + /* FTPRERR, WRITEFAILED, bindport (FTPSYSERR), HOSTERR, + FTPPORTERR */ + switch (err) + { + case FTPRERR: + logputs (LOG_VERBOSE, "\n"); + logputs (LOG_NOTQUIET, _("\ +Error in server response, closing control connection.\n")); + fd_close (csock); + con->csock = -1; + fd_close (dtsock); + fd_close (local_sock); + return err; + case WRITEFAILED: + logputs (LOG_VERBOSE, "\n"); + logputs (LOG_NOTQUIET, + _("Write failed, closing control connection.\n")); + fd_close (csock); + con->csock = -1; + fd_close (dtsock); + fd_close (local_sock); + return err; + case CONSOCKERR: + logputs (LOG_VERBOSE, "\n"); + logprintf (LOG_NOTQUIET, "socket: %s\n", strerror (errno)); + fd_close (csock); + con->csock = -1; + fd_close (dtsock); + fd_close (local_sock); + return err; + case FTPSYSERR: + logputs (LOG_VERBOSE, "\n"); + logprintf (LOG_NOTQUIET, _("Bind error (%s).\n"), + strerror (errno)); + fd_close (dtsock); + return err; + case FTPPORTERR: + logputs (LOG_VERBOSE, "\n"); + logputs (LOG_NOTQUIET, _("Invalid PORT.\n")); + fd_close (csock); + con->csock = -1; + fd_close (dtsock); + fd_close (local_sock); + return err; + case FTPOK: + break; + default: + abort (); + } /* port switch */ + if (!opt.server_response) + logputs (LOG_VERBOSE, _("done. ")); + } /* dtsock == -1 */ + } /* cmd & (DO_LIST | DO_RETR) */ + + /* Restart if needed. */ + if (restval && (cmd & DO_RETR)) + { + if (!opt.server_response) + logprintf (LOG_VERBOSE, "==> REST %s ... ", + number_to_static_string (restval)); + err = ftp_rest (csock, restval); + + /* FTPRERR, WRITEFAILED, FTPRESTFAIL */ + switch (err) + { + case FTPRERR: + logputs (LOG_VERBOSE, "\n"); + logputs (LOG_NOTQUIET, _("\ +Error in server response, closing control connection.\n")); + fd_close (csock); + con->csock = -1; + fd_close (dtsock); + fd_close (local_sock); + return err; + case WRITEFAILED: + logputs (LOG_VERBOSE, "\n"); + logputs (LOG_NOTQUIET, + _("Write failed, closing control connection.\n")); + fd_close (csock); + con->csock = -1; + fd_close (dtsock); + fd_close (local_sock); + return err; + case FTPRESTFAIL: + logputs (LOG_VERBOSE, _("\nREST failed, starting from scratch.\n")); + rest_failed = true; + break; + case FTPOK: + break; + default: + abort (); + } + if (err != FTPRESTFAIL && !opt.server_response) + logputs (LOG_VERBOSE, _("done. ")); + } /* restval && cmd & DO_RETR */ + + if (cmd & DO_RETR) + { + /* If we're in spider mode, don't really retrieve anything except + the directory listing and verify whether the given "file" exists. */ + if (opt.spider) + { + bool exists = false; + bool all_exist = true; + struct fileinfo *f; + uerr_t _res = ftp_get_listing (u, original_url, con, &f); + /* Set the DO_RETR command flag again, because it gets unset when + calling ftp_get_listing() and would otherwise cause an assertion + failure earlier on when this function gets repeatedly called + (e.g., when recursing). */ + con->cmd |= DO_RETR; + if (_res == RETROK) + { + while (f) + { + if (!strcmp (f->name, u->file)) + { + exists = true; + break; + } else { + all_exist = false; + } + f = f->next; + } + if (exists) + { + logputs (LOG_VERBOSE, "\n"); + logprintf (LOG_NOTQUIET, _("File %s exists.\n"), + quote (u->file)); + } + else + { + logputs (LOG_VERBOSE, "\n"); + logprintf (LOG_NOTQUIET, _("No such file %s.\n"), + quote (u->file)); + } + } + fd_close (csock); + con->csock = -1; + fd_close (dtsock); + fd_close (local_sock); + if (all_exist) { + return RETRFINISHED; + } else { + return FTPNSFOD; + } + } + + if (opt.verbose) + { + if (!opt.server_response) + { + if (restval) + logputs (LOG_VERBOSE, "\n"); + logprintf (LOG_VERBOSE, "==> RETR %s ... ", + quotearg_style (escape_quoting_style, u->file)); + } + } + + err = ftp_retr (csock, u->file); + /* FTPRERR, WRITEFAILED, FTPNSFOD */ + switch (err) + { + case FTPRERR: + logputs (LOG_VERBOSE, "\n"); + logputs (LOG_NOTQUIET, _("\ +Error in server response, closing control connection.\n")); + fd_close (csock); + con->csock = -1; + fd_close (dtsock); + fd_close (local_sock); + return err; + case WRITEFAILED: + logputs (LOG_VERBOSE, "\n"); + logputs (LOG_NOTQUIET, + _("Write failed, closing control connection.\n")); + fd_close (csock); + con->csock = -1; + fd_close (dtsock); + fd_close (local_sock); + return err; + case FTPNSFOD: + logputs (LOG_VERBOSE, "\n"); + logprintf (LOG_NOTQUIET, _("No such file %s.\n\n"), + quote (u->file)); + fd_close (dtsock); + fd_close (local_sock); + return err; + case FTPOK: + break; + default: + abort (); + } + + if (!opt.server_response) + logputs (LOG_VERBOSE, _("done.\n")); + + if (! got_expected_bytes) + expected_bytes = *last_expected_bytes; + } /* do retrieve */ + + if (cmd & DO_LIST) + { + if (!opt.server_response) + logputs (LOG_VERBOSE, "==> LIST ... "); + /* As Maciej W. Rozycki (macro@ds2.pg.gda.pl) says, `LIST' + without arguments is better than `LIST .'; confirmed by + RFC959. */ + err = ftp_list (csock, NULL, con->st&AVOID_LIST_A, con->st&AVOID_LIST, &list_a_used); + + /* FTPRERR, WRITEFAILED */ + switch (err) + { + case FTPRERR: + logputs (LOG_VERBOSE, "\n"); + logputs (LOG_NOTQUIET, _("\ +Error in server response, closing control connection.\n")); + fd_close (csock); + con->csock = -1; + fd_close (dtsock); + fd_close (local_sock); + return err; + case WRITEFAILED: + logputs (LOG_VERBOSE, "\n"); + logputs (LOG_NOTQUIET, + _("Write failed, closing control connection.\n")); + fd_close (csock); + con->csock = -1; + fd_close (dtsock); + fd_close (local_sock); + return err; + case FTPNSFOD: + logputs (LOG_VERBOSE, "\n"); + logprintf (LOG_NOTQUIET, _("No such file or directory %s.\n\n"), + quote (".")); + fd_close (dtsock); + fd_close (local_sock); + return err; + case FTPOK: + break; + default: + abort (); + } + if (!opt.server_response) + logputs (LOG_VERBOSE, _("done.\n")); + + if (! got_expected_bytes) + expected_bytes = *last_expected_bytes; + } /* cmd & DO_LIST */ + + if (!(cmd & (DO_LIST | DO_RETR)) || (opt.spider && !(cmd & DO_LIST))) + return RETRFINISHED; + + /* Some FTP servers return the total length of file after REST + command, others just return the remaining size. */ + if (passed_expected_bytes && restval && expected_bytes + && (expected_bytes == passed_expected_bytes - restval)) + { + DEBUGP (("Lying FTP server found, adjusting.\n")); + expected_bytes = passed_expected_bytes; + } + + /* If no transmission was required, then everything is OK. */ + if (!opt.ftp_pasv) /* we are not using passive mode so we need + to accept */ + { + /* Wait for the server to connect to the address we're waiting + at. */ + dtsock = accept_connection (local_sock); + if (dtsock < 0) + { + logprintf (LOG_NOTQUIET, "accept: %s\n", strerror (errno)); + return CONERROR; + } + } + + /* Open the file -- if output_stream is set, use it instead. */ + + /* 2005-04-17 SMS. + Note that having the output_stream ("-O") file opened in main + (main.c) rather limits the ability in VMS to open the file + differently for ASCII versus binary FTP here. (Of course, doing it + there allows a open failure to be detected immediately, without first + connecting to the server.) + */ + if (!output_stream || con->cmd & DO_LIST) + { +/* On VMS, alter the name as required. */ +#ifdef __VMS + char *targ; + + targ = ods_conform (con->target); + if (targ != con->target) + { + xfree (con->target); + con->target = targ; + } +#endif /* def __VMS */ + + mkalldirs (con->target); + if (opt.backups) + rotate_backups (con->target); + +/* 2005-04-15 SMS. + For VMS, define common fopen() optional arguments, and a handy macro + for use as a variable "binary" flag. + Elsewhere, define a constant "binary" flag. + Isn't it nice to have distinct text and binary file types? +*/ +/* 2011-09-30 SMS. + Added listing files to the set of non-"binary" (text, Stream_LF) + files. (Wget works either way, but other programs, like, say, text + editors, work better on listing files which have text attributes.) + Now we use "binary" attributes for a binary ("IMAGE") transfer, + unless "--ftp-stmlf" was specified, and we always use non-"binary" + (text, Stream_LF) attributes for a listing file, or for an ASCII + transfer. + Tidied the VMS-specific BIN_TYPE_xxx macros, and changed the call to + fopen_excl() (restored?) to use BIN_TYPE_FILE instead of "true". +*/ +#ifdef __VMS +# define BIN_TYPE_TRANSFER (type_char != 'A') +# define BIN_TYPE_FILE \ + ((!(cmd & DO_LIST)) && BIN_TYPE_TRANSFER && (opt.ftp_stmlf == 0)) +# define FOPEN_OPT_ARGS "fop=sqo", "acc", acc_cb, &open_id +# define FOPEN_OPT_ARGS_BIN "ctx=bin,stm", "rfm=fix", "mrs=512" FOPEN_OPT_ARGS +#else /* def __VMS */ +# define BIN_TYPE_FILE true +#endif /* def __VMS [else] */ + + if (restval && !(con->cmd & DO_LIST)) + { +#ifdef __VMS + int open_id; + + if (BIN_TYPE_FILE) + { + open_id = 3; + fp = fopen (con->target, "ab", FOPEN_OPT_ARGS_BIN); + } + else + { + open_id = 4; + fp = fopen (con->target, "a", FOPEN_OPT_ARGS); + } +#else /* def __VMS */ + fp = fopen (con->target, "ab"); +#endif /* def __VMS [else] */ + } + else if (opt.noclobber || opt.always_rest || opt.timestamping || opt.dirstruct + || opt.output_document || count > 0) + { + if (opt.unlink_requested && file_exists_p (con->target, NULL)) + { + if (unlink (con->target) < 0) + { + logprintf (LOG_NOTQUIET, "%s: %s\n", con->target, + strerror (errno)); + fd_close (csock); + con->csock = -1; + fd_close (dtsock); + fd_close (local_sock); + return UNLINKERR; + } + } + +#ifdef __VMS + int open_id; + + if (BIN_TYPE_FILE) + { + open_id = 5; + fp = fopen (con->target, "wb", FOPEN_OPT_ARGS_BIN); + } + else + { + open_id = 6; + fp = fopen (con->target, "w", FOPEN_OPT_ARGS); + } +#else /* def __VMS */ + fp = fopen (con->target, "wb"); +#endif /* def __VMS [else] */ + } + else + { + fp = fopen_excl (con->target, BIN_TYPE_FILE); + if (!fp && errno == EEXIST) + { + /* We cannot just invent a new name and use it (which is + what functions like unique_create typically do) + because we told the user we'd use this name. + Instead, return and retry the download. */ + logprintf (LOG_NOTQUIET, _("%s has sprung into existence.\n"), + con->target); + fd_close (csock); + con->csock = -1; + fd_close (dtsock); + fd_close (local_sock); + return FOPEN_EXCL_ERR; + } + } + if (!fp) + { + logprintf (LOG_NOTQUIET, "%s: %s\n", con->target, strerror (errno)); + fd_close (csock); + con->csock = -1; + fd_close (dtsock); + fd_close (local_sock); + return FOPENERR; + } + } + else + fp = output_stream; + + if (passed_expected_bytes) + { + print_length (passed_expected_bytes, restval, true); + expected_bytes = passed_expected_bytes; + /* for fd_read_body's progress bar */ + } + else if (expected_bytes) + print_length (expected_bytes, restval, false); + +#ifdef HAVE_SSL + if (u->scheme == SCHEME_FTPS && using_data_security) + { + /* We should try to restore the existing SSL session in the data connection + * and fall back to establishing a new session if the server doesn't want to restore it. + */ + if (!opt.ftps_resume_ssl || !ssl_connect_wget (dtsock, u->host, &csock)) + { + if (opt.ftps_resume_ssl) + logputs (LOG_NOTQUIET, "Server does not want to resume the SSL session. Trying with a new one.\n"); + if (!ssl_connect_wget (dtsock, u->host, NULL)) + { + fd_close (csock); + fd_close (dtsock); + err = CONERROR; + logputs (LOG_NOTQUIET, "Could not perform SSL handshake.\n"); + goto exit_error; + } + } + else + logputs (LOG_NOTQUIET, "Resuming SSL session in data connection.\n"); + + if (!ssl_check_certificate (dtsock, u->host)) + { + fd_close (csock); + fd_close (dtsock); + err = CONERROR; + goto exit_error; + } + } +#endif + + /* Get the contents of the document. */ + flags = 0; + if (restval && rest_failed) + flags |= rb_skip_startpos; + rd_size = 0; + res = fd_read_body (con->target, dtsock, fp, + expected_bytes ? expected_bytes - restval : 0, + restval, &rd_size, qtyread, &con->dltime, flags, warc_tmp); + + tms = datetime_str (time (NULL)); + tmrate = retr_rate (rd_size, con->dltime); + total_download_time += con->dltime; + +#ifdef ENABLE_XATTR + if (opt.enable_xattr) + set_file_metadata (u, NULL, fp); +#endif + + fd_close (local_sock); + /* Close the local file. */ + if (!output_stream || con->cmd & DO_LIST) + fclose (fp); + + /* If fd_read_body couldn't write to fp or warc_tmp, bail out. */ + if (res == -2 || (warc_tmp != NULL && res == -3)) + { + logprintf (LOG_NOTQUIET, _("%s: %s, closing control connection.\n"), + con->target, strerror (errno)); + fd_close (csock); + con->csock = -1; + fd_close (dtsock); + if (res == -2) + return FWRITEERR; + else if (res == -3) + return WARC_TMP_FWRITEERR; + } + else if (res == -1) + { + logprintf (LOG_NOTQUIET, _("%s (%s) - Data connection: %s; "), + tms, tmrate, fd_errstr (dtsock)); + if (opt.server_response) + logputs (LOG_ALWAYS, "\n"); + } + fd_close (dtsock); + + /* Get the server to tell us if everything is retrieved. */ + err = ftp_response (csock, &respline); + if (err != FTPOK) + { + /* The control connection is decidedly closed. Print the time + only if it hasn't already been printed. */ + if (res != -1) + logprintf (LOG_NOTQUIET, "%s (%s) - ", tms, tmrate); + logputs (LOG_NOTQUIET, _("Control connection closed.\n")); + /* If there is an error on the control connection, close it, but + return FTPRETRINT, since there is a possibility that the + whole file was retrieved nevertheless (but that is for + ftp_loop_internal to decide). */ + fd_close (csock); + con->csock = -1; + return FTPRETRINT; + } /* err != FTPOK */ + *last_expected_bytes = ftp_expected_bytes (respline); + /* If retrieval failed for any reason, return FTPRETRINT, but do not + close socket, since the control connection is still alive. If + there is something wrong with the control connection, it will + become apparent later. */ + if (*respline != '2') + { + if (res != -1) + logprintf (LOG_NOTQUIET, "%s (%s) - ", tms, tmrate); + logputs (LOG_NOTQUIET, _("Data transfer aborted.\n")); +#ifdef HAVE_SSL + if (!c_strncasecmp (respline, "425", 3) && u->scheme == SCHEME_FTPS) + { + logputs (LOG_NOTQUIET, "FTPS server rejects new SSL sessions in the data connection.\n"); + xfree (respline); + return FTPRESTFAIL; + } +#endif + xfree (respline); + return FTPRETRINT; + } + xfree (respline); + + if (res == -1) + { + /* What now? The data connection was erroneous, whereas the + response says everything is OK. We shall play it safe. */ + return FTPRETRINT; + } + + if (!(cmd & LEAVE_PENDING)) + { + /* Closing the socket is faster than sending 'QUIT' and the + effect is the same. */ + fd_close (csock); + con->csock = -1; + } + /* If it was a listing, and opt.server_response is true, + print it out. */ + if (con->cmd & DO_LIST) + { + if (opt.server_response) + { +/* 2005-02-25 SMS. + Much of this work may already have been done, but repeating it should + do no damage beyond wasting time. +*/ +/* On VMS, alter the name as required. */ +#ifdef __VMS + char *targ; + + targ = ods_conform (con->target); + if (targ != con->target) + { + xfree (con->target); + con->target = targ; + } +#endif /* def __VMS */ + + mkalldirs (con->target); + fp = fopen (con->target, "r"); + if (!fp) + logprintf (LOG_ALWAYS, "%s: %s\n", con->target, strerror (errno)); + else + { + char *line = NULL; + size_t bufsize = 0; + ssize_t len; + + /* The lines are being read with getline because of + no-buffering on opt.lfile. */ + while ((len = getline (&line, &bufsize, fp)) > 0) + { + while (len > 0 && (line[len - 1] == '\n' || line[len - 1] == '\r')) + line[--len] = '\0'; + logprintf (LOG_ALWAYS, "%s\n", + quotearg_style (escape_quoting_style, line)); + } + xfree (line); + fclose (fp); + } + } /* server_response */ + + /* 2013-10-17 Andrea Urbani (matfanjol) + < __LIST_A_EXPLANATION__ > + After the SYST command, looks if it knows that system. + If yes, wget will force the use of "LIST" or "LIST -a". + If no, wget will try, only the first time of each session, before the + "LIST -a" command and after the "LIST". + If "LIST -a" works and returns more or equal data of the "LIST", + "LIST -a" will be the standard list command for all the session. + If "LIST -a" fails or returns less data than "LIST" (think on the case + of an existing file called "-a"), "LIST" will be the standard list + command for all the session. + ("LIST -a" is used to get also the hidden files) + + */ + if (!(con->st & LIST_AFTER_LIST_A_CHECK_DONE)) + { + /* We still have to check "LIST" after the first "LIST -a" to see + if with "LIST" we get more data than "LIST -a", that means + "LIST -a" returned files/folders with "-a" name. */ + if (con->st & AVOID_LIST_A) + { + /* LIST was used in this cycle. + Let's see the result. */ + if (rd_size > previous_rd_size) + { + /* LIST returns more data than "LIST -a". + "LIST" is the official command to use. */ + con->st |= LIST_AFTER_LIST_A_CHECK_DONE; + DEBUGP (("LIST returned more data than \"LIST -a\": " + "I will use \"LIST\" as standard list command\n")); + } + else if (previous_rd_size > rd_size) + { + /* "LIST -a" returned more data then LIST. + "LIST -a" is the official command to use. */ + con->st |= LIST_AFTER_LIST_A_CHECK_DONE; + con->st |= AVOID_LIST; + con->st &= ~AVOID_LIST_A; + /* Sorry, please, download again the "LIST -a"... */ + try_again = true; + DEBUGP (("LIST returned less data than \"LIST -a\": I will " + "use \"LIST -a\" as standard list command\n")); + } + else + { + /* LIST and "LIST -a" return the same data. */ + if (rd_size == 0) + { + /* Same empty data. We will check both again because + we cannot check if "LIST -a" has returned an empty + folder instead of a folder content. */ + con->st &= ~AVOID_LIST_A; + } + else + { + /* Same data, so, better to take "LIST -a" that + shows also hidden files/folders (when present) */ + con->st |= LIST_AFTER_LIST_A_CHECK_DONE; + con->st |= AVOID_LIST; + con->st &= ~AVOID_LIST_A; + DEBUGP (("LIST returned the same amount of data of " + "\"LIST -a\": I will use \"LIST -a\" as standard " + "list command\n")); + } + } + } + else + { + /* In this cycle "LIST -a" should being used. Is it true? */ + if (list_a_used) + { + /* Yes, it is. + OK, let's save the amount of data and try again + with LIST */ + previous_rd_size = rd_size; + try_again = true; + con->st |= AVOID_LIST_A; + } + else + { + /* No: something happens and LIST was used. + This means "LIST -a" raises an error. */ + con->st |= LIST_AFTER_LIST_A_CHECK_DONE; + con->st |= AVOID_LIST_A; + DEBUGP (("\"LIST -a\" failed: I will use \"LIST\" " + "as standard list command\n")); + } + } + } + } + } while (try_again); + return RETRFINISHED; + +exit_error: + + /* If fp is a regular file, close and try to remove it */ + if (fp && (!output_stream || con->cmd & DO_LIST)) + fclose (fp); + return err; +} + +/* A one-file FTP loop. This is the part where FTP retrieval is + retried, and retried, and retried, and... + + This loop either gets commands from con, or (if ON_YOUR_OWN is + set), makes them up to retrieve the file given by the URL. */ +static uerr_t +ftp_loop_internal (struct url *u, struct url *original_url, struct fileinfo *f, + ccon *con, char **local_file, bool force_full_retrieve) +{ + int count, orig_lp; + wgint restval, len = 0, qtyread = 0; + char *tms, *locf; + const char *tmrate = NULL; + uerr_t err; + struct stat st; + + /* Declare WARC variables. */ + bool warc_enabled = (opt.warc_filename != NULL); + FILE *warc_tmp = NULL; + ip_address *warc_ip = NULL; + wgint last_expected_bytes = 0; + + /* Get the target, and set the name for the message accordingly. */ + if ((f == NULL) && (con->target)) + { + /* Explicit file (like ".listing"). */ + locf = con->target; + } + else + { + /* URL-derived file. Consider "-O file" name. */ + xfree (con->target); + con->target = url_file_name (opt.trustservernames || !original_url ? u : original_url, NULL); + if (!opt.output_document) + locf = con->target; + else + locf = opt.output_document; + } + + /* If the output_document was given, then this check was already done and + the file didn't exist. Hence the !opt.output_document */ + + /* If we receive .listing file it is necessary to determine system type of the ftp + server even if opn.noclobber is given. Thus we must ignore opt.noclobber in + order to establish connection with the server and get system type. */ + if (opt.noclobber && !opt.output_document && file_exists_p (con->target, NULL) + && !((con->cmd & DO_LIST) && !(con->cmd & DO_RETR))) + { + logprintf (LOG_VERBOSE, + _("File %s already there; not retrieving.\n"), quote (con->target)); + /* If the file is there, we suppose it's retrieved OK. */ + return RETROK; + } + + /* Remove it if it's a link. */ + remove_link (con->target); + + count = 0; + + if (con->st & ON_YOUR_OWN) + con->st = ON_YOUR_OWN; + + orig_lp = con->cmd & LEAVE_PENDING ? 1 : 0; + + /* THE loop. */ + do + { + /* Increment the pass counter. */ + ++count; + sleep_between_retrievals (count); + if (con->st & ON_YOUR_OWN) + { + con->cmd = 0; + con->cmd |= (DO_RETR | LEAVE_PENDING); + if (con->csock != -1) + con->cmd &= ~ (DO_LOGIN | DO_CWD); + else + con->cmd |= (DO_LOGIN | DO_CWD); + } + else /* not on your own */ + { + if (con->csock != -1) + con->cmd &= ~DO_LOGIN; + else + con->cmd |= DO_LOGIN; + if (con->st & DONE_CWD) + con->cmd &= ~DO_CWD; + else + con->cmd |= DO_CWD; + } + + /* For file RETR requests, we can write a WARC record. + We record the file contents to a temporary file. */ + if (warc_enabled && (con->cmd & DO_RETR) && warc_tmp == NULL) + { + warc_tmp = warc_tempfile (); + if (warc_tmp == NULL) + return WARC_TMP_FOPENERR; + + if (!con->proxy && con->csock != -1) + { + warc_ip = (ip_address *) alloca (sizeof (ip_address)); + socket_ip_address (con->csock, warc_ip, ENDPOINT_PEER); + } + } + + /* Decide whether or not to restart. */ + if (con->cmd & DO_LIST) + restval = 0; + else if (force_full_retrieve) + restval = 0; + else if (opt.start_pos >= 0) + restval = opt.start_pos; + else if (opt.always_rest + && stat (locf, &st) == 0 + && S_ISREG (st.st_mode)) + /* When -c is used, continue from on-disk size. (Can't use + hstat.len even if count>1 because we don't want a failed + first attempt to clobber existing data.) */ + restval = st.st_size; + else if (count > 1) + restval = qtyread; /* start where the previous run left off */ + else + restval = 0; + + /* Get the current time string. */ + tms = datetime_str (time (NULL)); + /* Print fetch message, if opt.verbose. */ + if (opt.verbose) + { + char *hurl = url_string (u, URL_AUTH_HIDE_PASSWD); + char tmp[256]; + strcpy (tmp, " "); + if (count > 1) + sprintf (tmp, _("(try:%2d)"), count); + logprintf (LOG_VERBOSE, "--%s-- %s\n %s => %s\n", + tms, hurl, tmp, quote (locf)); +#ifdef WINDOWS + ws_changetitle (hurl); +#endif + xfree (hurl); + } + /* Send getftp the proper length, if fileinfo was provided. */ + if (f && f->type != FT_SYMLINK) + len = f->size; + else + len = 0; + + /* If we are working on a WARC record, getftp should also write + to the warc_tmp file. */ + err = getftp (u, original_url, len, &qtyread, restval, con, count, + &last_expected_bytes, warc_tmp); + + if (con->csock == -1) + con->st &= ~DONE_CWD; + else + con->st |= DONE_CWD; + + switch (err) + { + case HOSTERR: case CONIMPOSSIBLE: case FWRITEERR: case FOPENERR: + case FTPNSFOD: case FTPLOGINC: case FTPNOPASV: case FTPNOAUTH: case FTPNOPBSZ: case FTPNOPROT: + case UNLINKERR: case WARC_TMP_FWRITEERR: case CONSSLERR: case CONTNOTSUPPORTED: +#ifdef HAVE_SSL + if (err == FTPNOAUTH) + logputs (LOG_NOTQUIET, "Server does not support AUTH TLS.\n"); + if (opt.ftps_implicit) + logputs (LOG_NOTQUIET, "Server does not like implicit FTPS connections.\n"); +#endif + /* Fatal errors, give up. */ + if (warc_tmp != NULL) + { + fclose (warc_tmp); + warc_tmp = NULL; + } + return err; + case CONSOCKERR: case CONERROR: case FTPSRVERR: case FTPRERR: + case WRITEFAILED: case FTPUNKNOWNTYPE: case FTPSYSERR: + case FTPPORTERR: case FTPLOGREFUSED: case FTPINVPASV: + case FOPEN_EXCL_ERR: + printwhat (count, opt.ntry); + /* non-fatal errors */ + if (err == FOPEN_EXCL_ERR) + { + /* Re-determine the file name. */ + xfree (con->target); + con->target = url_file_name (u, NULL); + locf = con->target; + } + continue; + case FTPRETRINT: + /* If the control connection was closed, the retrieval + will be considered OK if f->size == len. */ + if (!f || qtyread != f->size) + { + printwhat (count, opt.ntry); + continue; + } + break; + case RETRFINISHED: + /* Great! */ + break; + default: + /* Not as great. */ + abort (); + } + tms = datetime_str (time (NULL)); + if (!opt.spider) + tmrate = retr_rate (qtyread - restval, con->dltime); + + /* If we get out of the switch above without continue'ing, we've + successfully downloaded a file. Remember this fact. */ + downloaded_file (FILE_DOWNLOADED_NORMALLY, locf); + + if (con->st & ON_YOUR_OWN) + { + fd_close (con->csock); + con->csock = -1; + } + if (!opt.spider) + { + bool write_to_stdout = (opt.output_document && HYPHENP (opt.output_document)); + + logprintf (LOG_VERBOSE, + write_to_stdout + ? _("%s (%s) - written to stdout %s[%s]\n\n") + : _("%s (%s) - %s saved [%s]\n\n"), + tms, tmrate, + write_to_stdout ? "" : quote (locf), + number_to_static_string (qtyread)); + } + if (!opt.verbose && !opt.quiet) + { + /* Need to hide the password from the URL. The `if' is here + so that we don't do the needless allocation every + time. */ + char *hurl = url_string (u, URL_AUTH_HIDE_PASSWD); + logprintf (LOG_NONVERBOSE, "%s URL: %s [%s] -> \"%s\" [%d]\n", + tms, hurl, number_to_static_string (qtyread), locf, count); + xfree (hurl); + } + + if (warc_enabled && (con->cmd & DO_RETR)) + { + /* Create and store a WARC resource record for the retrieved file. */ + bool warc_res; + + warc_res = warc_write_resource_record (NULL, u->url, NULL, NULL, + warc_ip, NULL, warc_tmp, -1); + + if (! warc_res) + return WARC_ERR; + + /* warc_write_resource_record has also closed warc_tmp. */ + warc_tmp = NULL; + } + + if (con->cmd & DO_LIST) + /* This is a directory listing file. */ + { + if (!opt.remove_listing) + /* --dont-remove-listing was specified, so do count this towards the + number of bytes and files downloaded. */ + { + total_downloaded_bytes += qtyread; + numurls++; + } + + /* Deletion of listing files is not controlled by --delete-after, but + by the more specific option --dont-remove-listing, and the code + to do this deletion is in another function. */ + } + else if (!opt.spider) + /* This is not a directory listing file. */ + { + /* Unlike directory listing files, don't pretend normal files weren't + downloaded if they're going to be deleted. People seeding proxies, + for instance, may want to know how many bytes and files they've + downloaded through it. */ + total_downloaded_bytes += qtyread; + numurls++; + + if (opt.delete_after && !input_file_url (opt.input_filename)) + { + DEBUGP (("\ +Removing file due to --delete-after in ftp_loop_internal():\n")); + logprintf (LOG_VERBOSE, _("Removing %s.\n"), locf); + if (unlink (locf)) + logprintf (LOG_NOTQUIET, "unlink: %s\n", strerror (errno)); + } + } + + /* Restore the original leave-pendingness. */ + if (orig_lp) + con->cmd |= LEAVE_PENDING; + else + con->cmd &= ~LEAVE_PENDING; + + if (local_file) + *local_file = xstrdup (locf); + + if (warc_tmp != NULL) + { + fclose (warc_tmp); + warc_tmp = NULL; + } + + return RETROK; + } while (!opt.ntry || (count < opt.ntry)); + + if (con->csock != -1 && (con->st & ON_YOUR_OWN)) + { + fd_close (con->csock); + con->csock = -1; + } + + if (warc_tmp != NULL) + fclose (warc_tmp); + + return TRYLIMEXC; +} + +/* Return the directory listing in a reusable format. The directory + is specified in u->dir. */ +static uerr_t +ftp_get_listing (struct url *u, struct url *original_url, ccon *con, + struct fileinfo **f) +{ + uerr_t err; + char *uf; /* url file name */ + char *lf; /* list file name */ + char *old_target = con->target; + + con->st &= ~ON_YOUR_OWN; + con->cmd |= (DO_LIST | LEAVE_PENDING); + con->cmd &= ~DO_RETR; + + /* Find the listing file name. We do it by taking the file name of + the URL and replacing the last component with the listing file + name. */ + uf = url_file_name (u, NULL); + lf = file_merge (uf, LIST_FILENAME); + xfree (uf); + DEBUGP ((_("Using %s as listing tmp file.\n"), quote (lf))); + + con->target = xstrdup (lf); + xfree (lf); + err = ftp_loop_internal (u, original_url, NULL, con, NULL, false); + lf = xstrdup (con->target); + xfree (con->target); + con->target = old_target; + + if (err == RETROK) + { + *f = ftp_parse_ls (lf, con->rs); + if (opt.remove_listing) + { + if (unlink (lf)) + logprintf (LOG_NOTQUIET, "unlink: %s\n", strerror (errno)); + else + logprintf (LOG_VERBOSE, _("Removed %s.\n"), quote (lf)); + } + } + else + *f = NULL; + xfree (lf); + con->cmd &= ~DO_LIST; + return err; +} + +static uerr_t ftp_retrieve_dirs (struct url *, struct url *, + struct fileinfo *, ccon *); +static uerr_t ftp_retrieve_glob (struct url *, struct url *, ccon *, int); +static struct fileinfo *delelement (struct fileinfo *, struct fileinfo **); + +/* Retrieve a list of files given in struct fileinfo linked list. If + a file is a symbolic link, do not retrieve it, but rather try to + set up a similar link on the local disk, if the symlinks are + supported. + + If opt.recursive is set, after all files have been retrieved, + ftp_retrieve_dirs will be called to retrieve the directories. */ +static uerr_t +ftp_retrieve_list (struct url *u, struct url *original_url, + struct fileinfo *f, ccon *con) +{ + static int depth = 0; + uerr_t err; + struct fileinfo *orig; + wgint local_size; + time_t tml; + bool dlthis; /* Download this (file). */ + const char *actual_target = NULL; + bool force_full_retrieve = false; + + /* Increase the depth. */ + ++depth; + if (opt.reclevel != INFINITE_RECURSION && depth > opt.reclevel) + { + DEBUGP ((_("Recursion depth %d exceeded max. depth %d.\n"), + depth, opt.reclevel)); + --depth; + return RECLEVELEXC; + } + + assert (f != NULL); + orig = f; + + con->st &= ~ON_YOUR_OWN; + if (!(con->st & DONE_CWD)) + con->cmd |= DO_CWD; + else + con->cmd &= ~DO_CWD; + con->cmd |= (DO_RETR | LEAVE_PENDING); + + if (con->csock < 0) + con->cmd |= DO_LOGIN; + else + con->cmd &= ~DO_LOGIN; + + err = RETROK; /* in case it's not used */ + + while (f) + { + char *old_target, *ofile; + + if (opt.quota && total_downloaded_bytes > opt.quota) + { + --depth; + return QUOTEXC; + } + old_target = con->target; + + ofile = xstrdup (u->file); + url_set_file (u, f->name); + + con->target = url_file_name (u, NULL); + err = RETROK; + + dlthis = true; + if (opt.timestamping && f->type == FT_PLAINFILE) + { + struct stat st; + /* If conversion of HTML files retrieved via FTP is ever implemented, + we'll need to stat() <file>.orig here when -K has been specified. + I'm not implementing it now since files on an FTP server are much + more likely than files on an HTTP server to legitimately have a + .orig suffix. */ + if (!stat (con->target, &st)) + { + bool eq_size; + bool cor_val; + /* Else, get it from the file. */ + local_size = st.st_size; + tml = st.st_mtime; +#ifdef WINDOWS + /* Modification time granularity is 2 seconds for Windows, so + increase local time by 1 second for later comparison. */ + tml++; +#endif + /* Compare file sizes only for servers that tell us correct + values. Assume sizes being equal for servers that lie + about file size. */ + cor_val = (con->rs == ST_UNIX || con->rs == ST_WINNT); + eq_size = cor_val ? (local_size == f->size) : true; + if (f->tstamp <= tml && eq_size) + { + /* Remote file is older, file sizes can be compared and + are both equal. */ + logprintf (LOG_VERBOSE, _("\ +Remote file no newer than local file %s -- not retrieving.\n"), quote (con->target)); + dlthis = false; + } + else if (f->tstamp > tml) + { + /* Remote file is newer */ + force_full_retrieve = true; + logprintf (LOG_VERBOSE, _("\ +Remote file is newer than local file %s -- retrieving.\n\n"), + quote (con->target)); + } + else + { + /* Sizes do not match */ + logprintf (LOG_VERBOSE, _("\ +The sizes do not match (local %s) -- retrieving.\n\n"), + number_to_static_string (local_size)); + } + } + } /* opt.timestamping && f->type == FT_PLAINFILE */ + switch (f->type) + { + case FT_SYMLINK: + /* If opt.retr_symlinks is defined, we treat symlinks as + if they were normal files. There is currently no way + to distinguish whether they might be directories, and + follow them. */ + if (!opt.retr_symlinks) + { +#ifdef HAVE_SYMLINK + if (!f->linkto) + logputs (LOG_NOTQUIET, + _("Invalid name of the symlink, skipping.\n")); + else + { + struct stat st; + /* Check whether we already have the correct + symbolic link. */ + int rc = lstat (con->target, &st); + if (rc == 0) + { + size_t len = strlen (f->linkto) + 1; + if (S_ISLNK (st.st_mode)) + { + char *link_target = (char *)alloca (len); + size_t n = readlink (con->target, link_target, len); + if ((n == len - 1) + && (memcmp (link_target, f->linkto, n) == 0)) + { + logprintf (LOG_VERBOSE, _("\ +Already have correct symlink %s -> %s\n\n"), + quote (con->target), + quote (f->linkto)); + dlthis = false; + break; + } + } + } + logprintf (LOG_VERBOSE, _("Creating symlink %s -> %s\n"), + quote (con->target), quote (f->linkto)); + /* Unlink before creating symlink! */ + unlink (con->target); + if (symlink (f->linkto, con->target) == -1) + logprintf (LOG_NOTQUIET, "symlink: %s\n", strerror (errno)); + logputs (LOG_VERBOSE, "\n"); + } /* have f->linkto */ +#else /* not HAVE_SYMLINK */ + logprintf (LOG_NOTQUIET, + _("Symlinks not supported, skipping symlink %s.\n"), + quote (con->target)); +#endif /* not HAVE_SYMLINK */ + } + else /* opt.retr_symlinks */ + { + if (dlthis) + { + err = ftp_loop_internal (u, original_url, f, con, NULL, + force_full_retrieve); + } + } /* opt.retr_symlinks */ + break; + case FT_DIRECTORY: + if (!opt.recursive) + logprintf (LOG_NOTQUIET, _("Skipping directory %s.\n"), + quote (f->name)); + break; + case FT_PLAINFILE: + /* Call the retrieve loop. */ + if (dlthis) + { + err = ftp_loop_internal (u, original_url, f, con, NULL, + force_full_retrieve); + } + break; + case FT_UNKNOWN: + default: + logprintf (LOG_NOTQUIET, _("%s: unknown/unsupported file type.\n"), + quote (f->name)); + break; + } /* switch */ + + + /* 2004-12-15 SMS. + * Set permissions _before_ setting the times, as setting the + * permissions changes the modified-time, at least on VMS. + * Also, use the opt.output_document name here, too, as + * appropriate. (Do the test once, and save the result.) + */ + + set_local_file (&actual_target, con->target); + + /* If downloading a plain file, and the user requested it, then + set valid (non-zero) permissions. */ + if (dlthis && (actual_target != NULL) && + (f->type == FT_PLAINFILE) && opt.preserve_perm) + { + if (f->perms) + { + if (chmod (actual_target, f->perms)) + logprintf (LOG_NOTQUIET, + _("Failed to set permissions for %s.\n"), + actual_target); + } + else + DEBUGP (("Unrecognized permissions for %s.\n", actual_target)); + } + + /* Set the time-stamp information to the local file. Symlinks + are not to be stamped because it sets the stamp on the + original. :( */ + if (actual_target != NULL) + { + if (opt.useservertimestamps + && !(f->type == FT_SYMLINK && !opt.retr_symlinks) + && f->tstamp != -1 + && dlthis + && file_exists_p (con->target, NULL)) + { + touch (actual_target, f->tstamp); + } + else if (f->tstamp == -1) + logprintf (LOG_NOTQUIET, _("%s: corrupt time-stamp.\n"), + actual_target); + } + + xfree (con->target); + con->target = old_target; + + url_set_file (u, ofile); + xfree (ofile); + + /* Break on fatals. */ + if (err == QUOTEXC || err == HOSTERR || err == FWRITEERR + || err == WARC_ERR || err == WARC_TMP_FOPENERR + || err == WARC_TMP_FWRITEERR) + break; + con->cmd &= ~ (DO_CWD | DO_LOGIN); + f = f->next; + } + + /* We do not want to call ftp_retrieve_dirs here */ + if (opt.recursive && + !(opt.reclevel != INFINITE_RECURSION && depth >= opt.reclevel)) + err = ftp_retrieve_dirs (u, original_url, orig, con); + else if (opt.recursive) + DEBUGP ((_("Will not retrieve dirs since depth is %d (max %d).\n"), + depth, opt.reclevel)); + --depth; + return err; +} + +/* Retrieve the directories given in a file list. This function works + by simply going through the linked list and calling + ftp_retrieve_glob on each directory entry. The function knows + about excluded directories. */ +static uerr_t +ftp_retrieve_dirs (struct url *u, struct url *original_url, + struct fileinfo *f, ccon *con) +{ + char *container = NULL; + int container_size = 0; + + for (; f; f = f->next) + { + int size; + char *odir, *newdir; + + if (opt.quota && total_downloaded_bytes > opt.quota) + break; + if (f->type != FT_DIRECTORY) + continue; + + /* Allocate u->dir off stack, but reallocate only if a larger + string is needed. It's a pity there's no "realloca" for an + item on the bottom of the stack. */ + size = strlen (u->dir) + 1 + strlen (f->name) + 1; + if (size > container_size) + container = (char *)alloca (size); + newdir = container; + + odir = u->dir; + if (*odir == '\0' + || (*odir == '/' && *(odir + 1) == '\0')) + /* If ODIR is empty or just "/", simply append f->name to + ODIR. (In the former case, to preserve u->dir being + relative; in the latter case, to avoid double slash.) */ + sprintf (newdir, "%s%s", odir, f->name); + else + /* Else, use a separator. */ + sprintf (newdir, "%s/%s", odir, f->name); + + DEBUGP (("Composing new CWD relative to the initial directory.\n")); + DEBUGP ((" odir = '%s'\n f->name = '%s'\n newdir = '%s'\n\n", + odir, f->name, newdir)); + if (!accdir (newdir)) + { + logprintf (LOG_VERBOSE, _("\ +Not descending to %s as it is excluded/not-included.\n"), + quote (newdir)); + continue; + } + + con->st &= ~DONE_CWD; + + odir = xstrdup (u->dir); /* because url_set_dir will free + u->dir. */ + url_set_dir (u, newdir); + ftp_retrieve_glob (u, original_url, con, GLOB_GETALL); + url_set_dir (u, odir); + xfree (odir); + + /* Set the time-stamp? */ + } + + if (opt.quota && total_downloaded_bytes > opt.quota) + return QUOTEXC; + else + return RETROK; +} + +/* Return true if S has a leading '/' or contains '../' */ +static bool +has_insecure_name_p (const char *s) +{ + if (*s == '/') + return true; + + if (strstr (s, "../") != 0) + return true; + + return false; +} + +/* Test if the file node is invalid. This can occur due to malformed or + * maliciously crafted listing files being returned by the server. + * + * Currently, this function only tests if there are multiple entries in the + * listing file by the same name. However this function can be expanded as more + * such illegal listing formats are discovered. */ +static bool +is_invalid_entry (struct fileinfo *f) +{ + struct fileinfo *cur = f; + char *f_name = f->name; + + /* If the node we're currently checking has a duplicate later, we eliminate + * the current node and leave the next one intact. */ + while (cur->next) + { + cur = cur->next; + if (strcmp (f_name, cur->name) == 0) + return true; + } + return false; +} + +/* A near-top-level function to retrieve the files in a directory. + The function calls ftp_get_listing, to get a linked list of files. + Then it weeds out the file names that do not match the pattern. + ftp_retrieve_list is called with this updated list as an argument. + + If the argument ACTION is GLOB_GETONE, just download the file (but + first get the listing, so that the time-stamp is heeded); if it's + GLOB_GLOBALL, use globbing; if it's GLOB_GETALL, download the whole + directory. */ +static uerr_t +ftp_retrieve_glob (struct url *u, struct url *original_url, + ccon *con, int action) +{ + struct fileinfo *f, *start; + uerr_t res; + + con->cmd |= LEAVE_PENDING; + + res = ftp_get_listing (u, original_url, con, &start); + if (res != RETROK) + return res; + + // Set the function used for glob matching. + int (*matcher) (const char *, const char *, int) + = opt.ignore_case ? fnmatch_nocase : fnmatch; + + // Set the function used to compare strings +#ifdef __VMS + /* 2009-09-09 SMS. + * Odd-ball compiler ("HP C V7.3-009 on OpenVMS Alpha V7.3-2") + * bug causes spurious %CC-E-BADCONDIT complaint with this + * "?:" statement. (Different linkage attributes for strcmp() + * and strcasecmp().) Converting to "if" changes the + * complaint to %CC-W-PTRMISMATCH on "cmp = strcmp;". Adding + * the senseless type cast clears the complaint, and looks + * harmless. + */ + int (*cmp) (const char *, const char *) + = opt.ignore_case ? strcasecmp : (int (*)())strcmp; +#else /* def __VMS */ + int (*cmp) (const char *, const char *) + = opt.ignore_case ? strcasecmp : strcmp; +#endif /* def __VMS [else] */ + + f = start; + while (f) + { + + // Weed out files that do not confirm to the global rules given in + // opt.accepts and opt.rejects + if ((opt.accepts || opt.rejects) && + f->type != FT_DIRECTORY && !acceptable (f->name)) + { + logprintf (LOG_VERBOSE, _("Rejecting %s.\n"), + quote (f->name)); + f = delelement (f, &start); + continue; + } + + + // Identify and eliminate possibly harmful names or invalid entries. + if (has_insecure_name_p (f->name) || is_invalid_entry (f)) + { + logprintf (LOG_VERBOSE, _("Rejecting %s (Invalid Entry).\n"), + quote (f->name)); + f = delelement (f, &start); + continue; + } + + if (!accept_url (f->name)) + { + logprintf (LOG_VERBOSE, _("%s is excluded/not-included through regex.\n"), f->name); + f = delelement (f, &start); + continue; + } + + /* Now weed out the files that do not match our globbing pattern. + If we are dealing with a globbing pattern, that is. */ + if (*u->file) + { + if (action == GLOB_GLOBALL) + { + int matchres = matcher (u->file, f->name, 0); + if (matchres == -1) + { + logprintf (LOG_NOTQUIET, _("Error matching %s against %s: %s\n"), + u->file, quotearg_style (escape_quoting_style, f->name), + strerror (errno)); + freefileinfo (start); + return RETRBADPATTERN; + } + if (matchres == FNM_NOMATCH) + { + f = delelement (f, &start); /* delete the element from the list */ + continue; + } + } + else if (action == GLOB_GETONE) + { + if (0 != cmp(u->file, f->name)) + { + f = delelement (f, &start); + continue; + } + } + } + f = f->next; + } + + /* + * Now that preprocessing of the file listing is over, let's try to download + * all the remaining files in our listing. + */ + if (start) + { + /* Just get everything. */ + res = ftp_retrieve_list (u, original_url, start, con); + } + else + { + if (action == GLOB_GLOBALL) + { + /* No luck. */ + /* #### This message SUCKS. We should see what was the + reason that nothing was retrieved. */ + logprintf (LOG_VERBOSE, _("No matches on pattern %s.\n"), + quote (u->file)); + } + else if (action == GLOB_GETONE) /* GLOB_GETONE or GLOB_GETALL */ + { + /* Let's try retrieving it anyway. */ + con->st |= ON_YOUR_OWN; + res = ftp_loop_internal (u, original_url, NULL, con, NULL, false); + return res; + } + + /* If action == GLOB_GETALL, and the file list is empty, there's + no point in trying to download anything or in complaining about + it. (An empty directory should not cause complaints.) + */ + } + freefileinfo (start); + if (opt.quota && total_downloaded_bytes > opt.quota) + return QUOTEXC; + else + return res; +} + +/* The wrapper that calls an appropriate routine according to contents + of URL. Inherently, its capabilities are limited on what can be + encoded into a URL. */ +uerr_t +ftp_loop (struct url *u, struct url *original_url, char **local_file, int *dt, + struct url *proxy, bool recursive, bool glob) +{ + ccon con; /* FTP connection */ + uerr_t res; + + *dt = 0; + + xzero (con); + + con.csock = -1; + con.st = ON_YOUR_OWN; + con.rs = ST_UNIX; + con.id = NULL; + con.proxy = proxy; + + /* If the file name is empty, the user probably wants a directory + index. We'll provide one, properly HTML-ized. Unless + opt.htmlify is 0, of course. :-) */ + if (!*u->file && !recursive) + { + struct fileinfo *f; + res = ftp_get_listing (u, original_url, &con, &f); + + if (res == RETROK) + { + if (opt.htmlify && !opt.spider) + { + struct url *url_file = opt.trustservernames ? u : original_url; + char *filename = (opt.output_document + ? xstrdup (opt.output_document) + : (con.target ? xstrdup (con.target) + : url_file_name (url_file, NULL))); + res = ftp_index (filename, u, f); + if (res == FTPOK && opt.verbose) + { + if (!opt.output_document) + { + struct stat st; + wgint sz; + if (stat (filename, &st) == 0) + sz = st.st_size; + else + sz = -1; + logprintf (LOG_NOTQUIET, + _("Wrote HTML-ized index to %s [%s].\n"), + quote (filename), number_to_static_string (sz)); + } + else + logprintf (LOG_NOTQUIET, + _("Wrote HTML-ized index to %s.\n"), + quote (filename)); + } + xfree (filename); + } + freefileinfo (f); + } + } + else + { + bool ispattern = false; + if (glob) + { + /* Treat the URL as a pattern if the file name part of the + URL path contains wildcards. (Don't check for u->file + because it is unescaped and therefore doesn't leave users + the option to escape literal '*' as %2A.) */ + char *file_part = strrchr (u->path, '/'); + if (!file_part) + file_part = u->path; + ispattern = has_wildcards_p (file_part); + } + if (ispattern || recursive || opt.timestamping || opt.preserve_perm) + { + /* ftp_retrieve_glob is a catch-all function that gets called + if we need globbing, time-stamping, recursion or preserve + permissions. Its third argument is just what we really need. */ + res = ftp_retrieve_glob (u, original_url, &con, + ispattern ? GLOB_GLOBALL : GLOB_GETONE); + } + else + { + res = ftp_loop_internal (u, original_url, NULL, &con, local_file, false); + } + } + if (res == FTPOK) + res = RETROK; + if (res == RETROK) + *dt |= RETROKF; + /* If a connection was left, quench it. */ + if (con.csock != -1) + fd_close (con.csock); + xfree (con.id); + xfree (con.target); + return res; +} + +/* Delete an element from the fileinfo linked list. Returns the + address of the next element, or NULL if the list is exhausted. It + can modify the start of the list. */ +static struct fileinfo * +delelement (struct fileinfo *f, struct fileinfo **start) +{ + struct fileinfo *prev = f->prev; + struct fileinfo *next = f->next; + + xfree (f->name); + xfree (f->linkto); + xfree (f); + + if (next) + next->prev = prev; + if (prev) + prev->next = next; + else + *start = next; + return next; +} + +/* Free the fileinfo linked list of files. */ +void +freefileinfo (struct fileinfo *f) +{ + while (f) + { + struct fileinfo *next = f->next; + xfree (f->name); + if (f->linkto) + xfree (f->linkto); + xfree (f); + f = next; + } +} |