diff --git a/doc/wget.texi b/doc/wget.texi index 6666b273..f1244aa0 100644 --- a/doc/wget.texi +++ b/doc/wget.texi @@ -2008,6 +2008,43 @@ this option has no effect. Symbolic links are always traversed in this case. @end table +@section FTPS Options + +@table @samp +@item --ftps-implicit +This option tells Wget to use FTPS implicitly. Implicit FTPS consists of initializing +SSL/TLS from the very beginning of the control connection. This option does not send +an @code{AUTH TLS} command: it assumes the server speaks FTPS and directly starts an +SSL/TLS connection. If the attempt is successful, the session continues just like +regular FTPS (@code{PBSZ} and @code{PROT} are sent, etc.). +Implicit FTPS is no longer a requirement for FTPS implementations, and thus +many servers may not support it. If @samp{--ftps-implicit} is passed and no explicit +port number specified, the default port for implicit FTPS, 990, will be used, instead +of the default port for the "normal" (explicit) FTPS which is the same as that of FTP, +21. + +@item --no-ftps-resume-ssl +Do not resume the SSL/TLS session in the data channel. When starting a data connection, +Wget tries to resume the SSL/TLS session previously started in the control connection. +SSL/TLS session resumption avoids performing an entirely new handshake by reusing +the SSL/TLS parameters of a previous session. Typically, the FTPS servers want it that way, +so Wget does this by default. Under rare circumstances however, one might want to +start an entirely new SSL/TLS session in every data connection. +This is what @samp{--no-ftps-resume-ssl} is for. + +@item --ftps-clear-data-connection +All the data connections will be in plain text. Only the control connection will be +under SSL/TLS. Wget will send a @code{PROT C} command to achieve this, which must be +approved by the server. + +@item --ftps-fallback-to-ftp +Fall back to FTP if FTPS is not supported by the target server. For security reasons, +this option is not asserted by default. The default behaviour is to exit with an error. +If a server does not successfully reply to the initial @code{AUTH TLS} command, or in the +case of implicit FTPS, if the initial SSL/TLS connection attempt is rejected, it is +considered that such server does not support FTPS. +@end table + @node Recursive Retrieval Options, Recursive Accept/Reject Options, FTP Options, Invoking @section Recursive Retrieval Options diff --git a/src/ftp-basic.c b/src/ftp-basic.c index b058af92..bcb7847e 100644 --- a/src/ftp-basic.c +++ b/src/ftp-basic.c @@ -135,6 +135,23 @@ ftp_request (const char *command, const char *value) 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 @@ -144,16 +161,6 @@ ftp_login (int csock, const char *acc, const char *pass) char *request, *respline; int nwritten; - /* Get greeting. */ - err = ftp_response (csock, &respline); - if (err != FTPOK) - return err; - if (*respline != '2') - { - xfree (respline); - return FTPSRVERR; - } - xfree (respline); /* Send USER username. */ request = ftp_request ("USER", acc); nwritten = fd_write (csock, request, strlen (request), -1); @@ -422,6 +429,119 @@ ip_address_to_eprt_repr (const ip_address *addr, int port, char *buf, buf[buflen - 1] = '\0'; } +#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 */ + /* Bind a port and send the appropriate PORT command to the FTP server. Use acceptport after RETR, to get the socket of data connection. */ diff --git a/src/ftp.c b/src/ftp.c index 9dab99c9..80420ef5 100644 --- a/src/ftp.c +++ b/src/ftp.c @@ -44,6 +44,7 @@ as that of the covered work. */ #include "url.h" #include "retr.h" #include "ftp.h" +#include "ssl.h" #include "connect.h" #include "host.h" #include "netrc.h" @@ -237,6 +238,78 @@ print_length (wgint size, wgint start, bool authoritative) static uerr_t ftp_get_listing (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 @@ -260,6 +333,15 @@ getftp (struct url *u, wgint passed_expected_bytes, wgint *qtyread, 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); @@ -285,8 +367,34 @@ getftp (struct url *u, wgint passed_expected_bytes, wgint *qtyread, 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; + { + csock = con->csock; + using_data_security = con->st & DATA_CHANNEL_SECURITY; + } else /* cmd & DO_LOGIN */ { char *host = con->proxy ? con->proxy->host : u->host; @@ -308,6 +416,43 @@ getftp (struct url *u, wgint passed_expected_bytes, wgint *qtyread, 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)); @@ -365,6 +510,46 @@ Error in server response, closing control connection.\n")); 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 ... ", prot); + if ((err = ftp_prot (csock, prot)) == FTPNOPROT) + { + logprintf (LOG_NOTQUIET, _("Server did not accept the 'PROT %c' command.\n"), 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 ... "); @@ -1313,6 +1498,36 @@ Error in server response, closing control connection.\n")); 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); + logputs (LOG_NOTQUIET, "Could not perform SSL handshake.\n"); + return CONERROR; + } + } + else + logputs (LOG_NOTQUIET, "Resuming SSL session in data connection.\n"); + + if (!ssl_check_certificate (dtsock, u->host)) + { + fd_close (csock); + fd_close (dtsock); + return CONERROR; + } + } +#endif + /* Get the contents of the document. */ flags = 0; if (restval && rest_failed) @@ -1377,10 +1592,18 @@ Error in server response, closing control connection.\n")); become apparent later. */ if (*respline != '2') { - xfree (respline); 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); @@ -1700,8 +1923,14 @@ ftp_loop_internal (struct url *u, struct fileinfo *f, ccon *con, char **local_fi switch (err) { case HOSTERR: case CONIMPOSSIBLE: case FWRITEERR: case FOPENERR: - case FTPNSFOD: case FTPLOGINC: case FTPNOPASV: case CONTNOTSUPPORTED: - case UNLINKERR: case WARC_TMP_FWRITEERR: + 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); diff --git a/src/ftp.h b/src/ftp.h index 737ce2ec..84ded269 100644 --- a/src/ftp.h +++ b/src/ftp.h @@ -33,6 +33,7 @@ as that of the covered work. */ #define FTP_H #include "host.h" +#include "url.h" /* System types. */ enum stype @@ -53,10 +54,27 @@ enum ustype UST_OTHER }; +#ifdef HAVE_SSL +/* Data channel protection levels (to be used with PBSZ) */ +enum prot_level +{ + PROT_CLEAR = 'C', + PROT_SAFE = 'S', + PROT_CONFIDENTIAL = 'E', + PROT_PRIVATE = 'P' +}; +#endif + uerr_t ftp_response (int, char **); +uerr_t ftp_greeting (int); uerr_t ftp_login (int, const char *, const char *); uerr_t ftp_port (int, int *); uerr_t ftp_pasv (int, ip_address *, int *); +#ifdef HAVE_SSL +uerr_t ftp_auth (int, enum url_scheme); +uerr_t ftp_pbsz (int, int); +uerr_t ftp_prot (int, enum prot_level); +#endif #ifdef ENABLE_IPV6 uerr_t ftp_lprt (int, int *); uerr_t ftp_lpsv (int, ip_address *, int *); @@ -142,11 +160,12 @@ enum wget_ftp_fstatus AVOID_LIST = 0x0008, /* It tells us if during this session we have to avoid to use "LIST". */ - LIST_AFTER_LIST_A_CHECK_DONE = 0x0010 + LIST_AFTER_LIST_A_CHECK_DONE = 0x0010, /* It tells us if we have already checked "LIST" after the first "LIST -a" to handle the case of file/folders named "-a". */ + DATA_CHANNEL_SECURITY = 0x0020 /* Establish a secure data channel */ }; struct fileinfo *ftp_parse_ls (const char *, const enum stype); diff --git a/src/gnutls.c b/src/gnutls.c index be043420..a38301a0 100644 --- a/src/gnutls.c +++ b/src/gnutls.c @@ -219,6 +219,7 @@ cert to be of the same type.\n")); struct wgnutls_transport_context { gnutls_session_t session; /* GnuTLS session handle */ + gnutls_datum_t *session_data; int last_error; /* last error returned by read/write/... */ /* Since GnuTLS doesn't support the equivalent to recv(..., @@ -405,6 +406,11 @@ wgnutls_close (int fd, void *arg) { struct wgnutls_transport_context *ctx = arg; /*gnutls_bye (ctx->session, GNUTLS_SHUT_RDWR);*/ + if (ctx->session_data) + { + gnutls_free (ctx->session_data->data); + gnutls_free (ctx->session_data); + } gnutls_deinit (ctx->session); xfree (ctx); close (fd); @@ -420,7 +426,7 @@ static struct transport_implementation wgnutls_transport = }; bool -ssl_connect_wget (int fd, const char *hostname) +ssl_connect_wget (int fd, const char *hostname, int *continue_session) { #ifdef F_GETFL int flags = 0; @@ -531,6 +537,27 @@ ssl_connect_wget (int fd, const char *hostname) return false; } + if (continue_session) + { + ctx = (struct wgnutls_transport_context *) fd_transport_context (*continue_session); + if (!gnutls_session_is_resumed (session)) + { + if (!ctx || !ctx->session_data || gnutls_session_set_data (session, ctx->session_data->data, ctx->session_data->size)) + { + /* server does not want to continue the session */ + gnutls_free (ctx->session_data->data); + gnutls_free (ctx->session_data); + gnutls_deinit (session); + return false; + } + } + else + { + logputs (LOG_ALWAYS, "SSL session has already been resumed. Continuing.\n"); + continue_session = NULL; + } + } + if (opt.connect_timeout) { #ifdef F_GETFL @@ -612,7 +639,13 @@ ssl_connect_wget (int fd, const char *hostname) } ctx = xnew0 (struct wgnutls_transport_context); + ctx->session_data = xnew0 (gnutls_datum_t); ctx->session = session; + if (gnutls_session_get_data2 (session, ctx->session_data)) + { + xfree (ctx->session_data); + logprintf (LOG_NOTQUIET, "WARNING: Could not save SSL session data for socket %d\n", fd); + } fd_register_transport (fd, &wgnutls_transport, ctx); return true; } diff --git a/src/http.c b/src/http.c index 8a36f41e..4c1052d5 100644 --- a/src/http.c +++ b/src/http.c @@ -2141,7 +2141,7 @@ establish_connection (struct url *u, struct url **conn_ref, if (conn->scheme == SCHEME_HTTPS) { - if (!ssl_connect_wget (sock, u->host)) + if (!ssl_connect_wget (sock, u->host, NULL)) { CLOSE_INVALIDATE (sock); return CONSSLERR; @@ -2742,6 +2742,9 @@ metalink_from_http (const struct response *resp, const struct http_stat *hs, case SCHEME_HTTPS: mres.type = xstrdup ("https"); break; + case SCHEME_FTPS: + mres.type = xstrdup ("ftps"); + break; #endif case SCHEME_FTP: mres.type = xstrdup ("ftp"); diff --git a/src/init.c b/src/init.c index ea074cc9..dd1506ce 100644 --- a/src/init.c +++ b/src/init.c @@ -188,6 +188,12 @@ static const struct { { "ftppasswd", &opt.ftp_passwd, cmd_string }, /* deprecated */ { "ftppassword", &opt.ftp_passwd, cmd_string }, { "ftpproxy", &opt.ftp_proxy, cmd_string }, +#ifdef HAVE_SSL + { "ftpscleardataconnection", &opt.ftps_clear_data_connection, cmd_boolean }, + { "ftpsfallbacktoftp", &opt.ftps_fallback_to_ftp, cmd_boolean }, + { "ftpsimplicit", &opt.ftps_implicit, cmd_boolean }, + { "ftpsresumessl", &opt.ftps_resume_ssl, cmd_boolean }, +#endif #ifdef __VMS { "ftpstmlf", &opt.ftp_stmlf, cmd_boolean }, #endif /* def __VMS */ @@ -408,6 +414,10 @@ defaults (void) #ifdef HAVE_SSL opt.check_cert = true; + opt.ftps_resume_ssl = true; + opt.ftps_fallback_to_ftp = false; + opt.ftps_implicit = false; + opt.ftps_clear_data_connection = false; #endif /* The default for file name restriction defaults to the OS type. */ diff --git a/src/main.c b/src/main.c index 3e74cd7d..92d1bee2 100644 --- a/src/main.c +++ b/src/main.c @@ -293,6 +293,12 @@ static struct cmdline_option option_data[] = { "ftp-stmlf", 0, OPT_BOOLEAN, "ftpstmlf", -1 }, #endif /* def __VMS */ { "ftp-user", 0, OPT_VALUE, "ftpuser", -1 }, +#ifdef HAVE_SSL + { "ftps-clear-data-connection", 0, OPT_BOOLEAN, "ftpscleardataconnection", -1 }, + { "ftps-fallback-to-ftp", 0, OPT_BOOLEAN, "ftpsfallbacktoftp", -1 }, + { "ftps-implicit", 0, OPT_BOOLEAN, "ftpsimplicit", -1 }, + { "ftps-resume-ssl", 0, OPT_BOOLEAN, "ftpsresumessl", -1 }, +#endif { "glob", 0, OPT_BOOLEAN, "glob", -1 }, { "header", 0, OPT_VALUE, "header", -1 }, { "help", 'h', OPT_FUNCALL, (void *)print_help, no_argument }, @@ -820,6 +826,20 @@ FTP options:\n"), --retr-symlinks when recursing, get linked-to files (not dir)\n"), "\n", +#ifdef HAVE_SSL + N_("\ +FTPS options:\n"), + N_("\ + --ftps-implicit use implicit FTPS (default port is 990)\n"), + N_("\ + --ftps-resume-ssl resume the SSL/TLS session started in the control connection when\n" + " opening a data connection\n"), + N_("\ + --ftps-clear-data-connection cipher the control channel only; all the data will be in plaintext\n"), + N_("\ + --ftps-fallback-to-ftp fall back to FTP if FTPS is not supported in the target server\n"), +#endif + N_("\ WARC options:\n"), N_("\ @@ -1810,12 +1830,13 @@ outputting to a regular file.\n")); else { if ((opt.recursive || opt.page_requisites) - && (url_scheme (*t) != SCHEME_FTP || url_uses_proxy (url_parsed))) + && ((url_scheme (*t) != SCHEME_FTP && url_scheme (*t) != SCHEME_FTPS) + || url_uses_proxy (url_parsed))) { int old_follow_ftp = opt.follow_ftp; /* Turn opt.follow_ftp on in case of recursive FTP retrieval */ - if (url_scheme (*t) == SCHEME_FTP) + if (url_scheme (*t) == SCHEME_FTP || url_scheme (*t) == SCHEME_FTPS) opt.follow_ftp = 1; retrieve_tree (url_parsed, NULL); diff --git a/src/metalink.h b/src/metalink.h index ec91b07b..e98c2109 100644 --- a/src/metalink.h +++ b/src/metalink.h @@ -34,7 +34,7 @@ as that of the covered work. */ #ifdef HAVE_SSL # define RES_TYPE_SUPPORTED(x)\ - ((!x) || !strcmp (x, "ftp") || !strcmp (x, "http") || !strcmp (x, "https")) + ((!x) || !strcmp (x, "http") || !strcmp (x, "https") || !strcmp (x, "ftp") || !strcmp (x, "ftps")) #else # define RES_TYPE_SUPPORTED(x)\ ((!x) || !strcmp (x, "ftp") || !strcmp (x, "http")) diff --git a/src/openssl.c b/src/openssl.c index 3ac0f44c..4876048d 100644 --- a/src/openssl.c +++ b/src/openssl.c @@ -334,6 +334,7 @@ ssl_init (void) struct openssl_transport_context { SSL *conn; /* SSL connection handle */ + SSL_SESSION *sess; /* SSL session info */ char *last_error; /* last error printed with openssl_errstr */ }; @@ -514,7 +515,7 @@ ssl_connect_with_timeout_callback(void *arg) Returns true on success, false on failure. */ bool -ssl_connect_wget (int fd, const char *hostname) +ssl_connect_wget (int fd, const char *hostname, int *continue_session) { SSL *conn; struct scwt_context scwt_ctx; @@ -527,7 +528,7 @@ ssl_connect_wget (int fd, const char *hostname) if (!conn) goto error; #if OPENSSL_VERSION_NUMBER >= 0x0090806fL && !defined(OPENSSL_NO_TLSEXT) - /* If the SSL library was build with support for ServerNameIndication + /* If the SSL library was built with support for ServerNameIndication then use it whenever we have a hostname. If not, don't, ever. */ if (! is_valid_ip_address (hostname)) { @@ -539,6 +540,14 @@ ssl_connect_wget (int fd, const char *hostname) } #endif + if (continue_session) + { + /* attempt to resume a previous SSL session */ + ctx = (struct openssl_transport_context *) fd_transport_context (*continue_session); + if (!ctx || !ctx->sess || !SSL_set_session (conn, ctx->sess)) + goto error; + } + #ifndef FD_TO_SOCKET # define FD_TO_SOCKET(X) (X) #endif @@ -557,6 +566,9 @@ ssl_connect_wget (int fd, const char *hostname) ctx = xnew0 (struct openssl_transport_context); ctx->conn = conn; + ctx->sess = SSL_get0_session (conn); + if (!ctx->sess) + logprintf (LOG_NOTQUIET, "WARNING: Could not save SSL session data for socket %d\n", fd); /* Register FD with Wget's transport layer, i.e. arrange that our functions are used for reading, writing, and polling. */ diff --git a/src/options.h b/src/options.h index 24ddbb57..0bb7d715 100644 --- a/src/options.h +++ b/src/options.h @@ -229,6 +229,10 @@ struct options char *random_file; /* file with random data to seed the PRNG */ char *egd_file; /* file name of the egd daemon socket */ bool https_only; /* whether to follow HTTPS only */ + bool ftps_resume_ssl; + bool ftps_fallback_to_ftp; + bool ftps_implicit; + bool ftps_clear_data_connection; #endif /* HAVE_SSL */ bool cookies; /* whether cookies are used. */ diff --git a/src/recur.c b/src/recur.c index ce553628..25cdbb7d 100644 --- a/src/recur.c +++ b/src/recur.c @@ -610,7 +610,7 @@ download_child (const struct urlpos *upos, struct url *parent, int depth, u_scheme_like_http = schemes_are_similar_p (u->scheme, SCHEME_HTTP); /* 1. Schemes other than HTTP are normally not recursed into. */ - if (!u_scheme_like_http && !(u->scheme == SCHEME_FTP && opt.follow_ftp)) + if (!u_scheme_like_http && !((u->scheme == SCHEME_FTP || u->scheme == SCHEME_FTPS) && opt.follow_ftp)) { DEBUGP (("Not following non-HTTP schemes.\n")); reason = WG_RR_NONHTTP; @@ -832,12 +832,13 @@ write_reject_log_url (FILE *fp, const struct url *url) switch (url->scheme) { - case SCHEME_HTTP: scheme_str = "SCHEME_HTTP"; break; - #ifdef HAVE_SSL - case SCHEME_HTTPS: scheme_str = "SCHEME_HTTPS"; break; - #endif - case SCHEME_FTP: scheme_str = "SCHEME_FTP"; break; - default: scheme_str = "SCHEME_INVALID"; break; + case SCHEME_HTTP: scheme_str = "SCHEME_HTTP"; break; +#ifdef HAVE_SSL + case SCHEME_HTTPS: scheme_str = "SCHEME_HTTPS"; break; + case SCHEME_FTPS: scheme_str = "SCHEME_FTPS"; break; +#endif + case SCHEME_FTP: scheme_str = "SCHEME_FTP"; break; + default: scheme_str = "SCHEME_INVALID"; break; } fprintf (fp, "%s\t%s\t%s\t%i\t%s\t%s\t%s\t%s", diff --git a/src/retr.c b/src/retr.c index e2b737e0..318b09c3 100644 --- a/src/retr.c +++ b/src/retr.c @@ -817,7 +817,11 @@ retrieve_url (struct url * orig_parsed, const char *origurl, char **file, result = http_loop (u, orig_parsed, &mynewloc, &local_file, refurl, dt, proxy_url, iri); } - else if (u->scheme == SCHEME_FTP) + else if (u->scheme == SCHEME_FTP +#ifdef HAVE_SSL + || u->scheme == SCHEME_FTPS +#endif + ) { /* If this is a redirection, temporarily turn off opt.ftp_glob and opt.recursive, both being undesirable when following @@ -833,7 +837,7 @@ retrieve_url (struct url * orig_parsed, const char *origurl, char **file, FTP. In these cases we must decide whether the text is HTML according to the suffix. The HTML suffixes are `.html', `.htm' and a few others, case-insensitive. */ - if (redirection_count && local_file && u->scheme == SCHEME_FTP) + if (redirection_count && local_file && (u->scheme == SCHEME_FTP || u->scheme == SCHEME_FTPS)) { if (has_html_suffix_p (local_file)) *dt |= TEXTHTML; @@ -1095,12 +1099,12 @@ retrieve_from_file (const char *file, bool html, int *count) proxy = getproxy (cur_url->url); if ((opt.recursive || opt.page_requisites) - && (cur_url->url->scheme != SCHEME_FTP || proxy)) + && ((cur_url->url->scheme != SCHEME_FTP && cur_url->url->scheme != SCHEME_FTPS) || proxy)) { int old_follow_ftp = opt.follow_ftp; /* Turn opt.follow_ftp on in case of recursive FTP retrieval */ - if (cur_url->url->scheme == SCHEME_FTP) + if (cur_url->url->scheme == SCHEME_FTP || cur_url->url->scheme == SCHEME_FTPS) opt.follow_ftp = 1; status = retrieve_tree (parsed_url ? parsed_url : cur_url->url, @@ -1281,6 +1285,9 @@ getproxy (struct url *u) case SCHEME_HTTPS: proxy = opt.https_proxy ? opt.https_proxy : getenv ("https_proxy"); break; + case SCHEME_FTPS: + proxy = opt.ftp_proxy ? opt.ftp_proxy : getenv ("ftps_proxy"); + break; #endif case SCHEME_FTP: proxy = opt.ftp_proxy ? opt.ftp_proxy : getenv ("ftp_proxy"); diff --git a/src/ssl.h b/src/ssl.h index 23a16ecc..eeb6b347 100644 --- a/src/ssl.h +++ b/src/ssl.h @@ -33,7 +33,7 @@ as that of the covered work. */ #define GEN_SSLFUNC_H bool ssl_init (void); -bool ssl_connect_wget (int, const char *); +bool ssl_connect_wget (int, const char *, int *); bool ssl_check_certificate (int, const char *); #endif /* GEN_SSLFUNC_H */ diff --git a/src/url.c b/src/url.c index d8d6d95b..56079cdb 100644 --- a/src/url.c +++ b/src/url.c @@ -78,6 +78,13 @@ static struct scheme_data supported_schemes[] = { "https", "https://", DEFAULT_HTTPS_PORT, scm_has_query|scm_has_fragment }, #endif { "ftp", "ftp://", DEFAULT_FTP_PORT, scm_has_params|scm_has_fragment }, +#ifdef HAVE_SSL + /* + * Explicit FTPS uses the same port as FTP. + * Implicit FTPS has its own port (990), but it is disabled by default. + */ + { "ftps", "ftps://", DEFAULT_FTP_PORT, scm_has_params|scm_has_fragment }, +#endif /* SCHEME_INVALID */ { NULL, NULL, -1, 0 } @@ -1772,7 +1779,7 @@ path_simplify (enum url_scheme scheme, char *path) else if (h[0] == '.' && h[1] == '.' && (h[2] == '/' || h[2] == '\0')) { /* Handle "../" by retreating the tortoise by one path - element -- but not past beggining. */ + element -- but not past beginning. */ if (t > beg) { /* Move backwards until T hits the beginning of the @@ -1780,7 +1787,7 @@ path_simplify (enum url_scheme scheme, char *path) for (--t; t > beg && t[-1] != '/'; t--) ; } - else if (scheme == SCHEME_FTP) + else if (scheme == SCHEME_FTP || scheme == SCHEME_FTPS) { /* If we're at the beginning, copy the "../" literally and move the beginning so a later ".." doesn't remove diff --git a/src/url.h b/src/url.h index a543f3de..7c777373 100644 --- a/src/url.h +++ b/src/url.h @@ -36,6 +36,7 @@ as that of the covered work. */ #define DEFAULT_HTTP_PORT 80 #define DEFAULT_FTP_PORT 21 #define DEFAULT_HTTPS_PORT 443 +#define DEFAULT_FTPS_IMPLICIT_PORT 990 /* This represents how many characters less than the OS max name length a file * should be. More precisely, a file name should be at most @@ -70,6 +71,9 @@ enum url_scheme { SCHEME_HTTPS, #endif SCHEME_FTP, +#ifdef HAVE_SSL + SCHEME_FTPS, +#endif SCHEME_INVALID }; diff --git a/src/wget.h b/src/wget.h index c04761d5..ab6270f1 100644 --- a/src/wget.h +++ b/src/wget.h @@ -349,7 +349,8 @@ typedef enum FTPSRVERR, FTPRETRINT, FTPRESTFAIL, URLERROR, FOPENERR, FOPEN_EXCL_ERR, FWRITEERR, HEOF, GATEWAYTIMEOUT, HERR, RETROK, RECLEVELEXC, WRONGCODE, - FTPINVPASV, FTPNOPASV, CONTNOTSUPPORTED, RETRUNNEEDED, RETRFINISHED, + FTPINVPASV, FTPNOPASV, FTPNOPBSZ, FTPNOPROT, FTPNOAUTH, + CONTNOTSUPPORTED, RETRUNNEEDED, RETRFINISHED, READERR, TRYLIMEXC, FILEBADFILE, RANGEERR, RETRBADPATTERN, PROXERR, AUTHFAILED, QUOTEXC, WRITEFAILED, SSLINITFAILED, VERIFCERTERR,