From e996e322ffd42aaa051602da182d03178d0f13e1 Mon Sep 17 00:00:00 2001 From: Giuseppe Scrivano Date: Mon, 6 Jun 2016 21:20:24 +0200 Subject: [PATCH] ftp: understand --trust-server-names on a HTTP->FTP redirect If not --trust-server-names is used, FTP will also get the destination file name from the original url specified by the user instead of the redirected url. Closes CVE-2016-4971. * src/ftp.c (ftp_get_listing): Add argument original_url. (getftp): Likewise. (ftp_loop_internal): Likewise. Use original_url to generate the file name if --trust-server-names is not provided. (ftp_retrieve_glob): Likewise. (ftp_loop): Likewise. Signed-off-by: Giuseppe Scrivano Upstream-Status: Backport CVE: CVE-2016-4971 Signed-off-by: Armin Kuster --- src/ftp.c | 71 +++++++++++++++++++++++++++++++++++++------------------------- src/ftp.h | 3 ++- src/retr.c | 3 ++- 3 files changed, 47 insertions(+), 30 deletions(-) Index: wget-1.16.3/src/ftp.c =================================================================== --- wget-1.16.3.orig/src/ftp.c +++ wget-1.16.3/src/ftp.c @@ -235,14 +235,15 @@ print_length (wgint size, wgint start, b logputs (LOG_VERBOSE, !authoritative ? _(" (unauthoritative)\n") : "\n"); } -static uerr_t ftp_get_listing (struct url *, ccon *, struct fileinfo **); +static uerr_t ftp_get_listing (struct url *, struct url *, ccon *, struct fileinfo **); /* 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, wgint passed_expected_bytes, wgint *qtyread, +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) { @@ -996,7 +997,7 @@ Error in server response, closing contro { bool exists = false; struct fileinfo *f; - uerr_t _res = ftp_get_listing (u, con, &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 @@ -1540,8 +1541,8 @@ Error in server response, closing contro 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 fileinfo *f, ccon *con, char **local_file, - bool force_full_retrieve) +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; @@ -1566,7 +1567,7 @@ ftp_loop_internal (struct url *u, struct { /* URL-derived file. Consider "-O file" name. */ xfree (con->target); - con->target = url_file_name (u, NULL); + con->target = url_file_name (opt.trustservernames || !original_url ? u : original_url, NULL); if (!opt.output_document) locf = con->target; else @@ -1684,8 +1685,8 @@ ftp_loop_internal (struct url *u, struct /* If we are working on a WARC record, getftp should also write to the warc_tmp file. */ - err = getftp (u, len, &qtyread, restval, con, count, &last_expected_bytes, - warc_tmp); + err = getftp (u, original_url, len, &qtyread, restval, con, count, + &last_expected_bytes, warc_tmp); if (con->csock == -1) con->st &= ~DONE_CWD; @@ -1838,7 +1839,8 @@ Removing file due to --delete-after in f /* Return the directory listing in a reusable format. The directory is specifed in u->dir. */ static uerr_t -ftp_get_listing (struct url *u, ccon *con, struct fileinfo **f) +ftp_get_listing (struct url *u, struct url *original_url, ccon *con, + struct fileinfo **f) { uerr_t err; char *uf; /* url file name */ @@ -1859,7 +1861,7 @@ ftp_get_listing (struct url *u, ccon *co con->target = xstrdup (lf); xfree (lf); - err = ftp_loop_internal (u, NULL, con, NULL, false); + err = ftp_loop_internal (u, original_url, NULL, con, NULL, false); lf = xstrdup (con->target); xfree (con->target); con->target = old_target; @@ -1882,8 +1884,9 @@ ftp_get_listing (struct url *u, ccon *co return err; } -static uerr_t ftp_retrieve_dirs (struct url *, struct fileinfo *, ccon *); -static uerr_t ftp_retrieve_glob (struct url *, ccon *, int); +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 **); static void freefileinfo (struct fileinfo *f); @@ -1895,7 +1898,8 @@ static void freefileinfo (struct fileinf 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 fileinfo *f, ccon *con) +ftp_retrieve_list (struct url *u, struct url *original_url, + struct fileinfo *f, ccon *con) { static int depth = 0; uerr_t err; @@ -2056,7 +2060,10 @@ Already have correct symlink %s -> %s\n\ else /* opt.retr_symlinks */ { if (dlthis) - err = ftp_loop_internal (u, f, con, NULL, force_full_retrieve); + { + err = ftp_loop_internal (u, original_url, f, con, NULL, + force_full_retrieve); + } } /* opt.retr_symlinks */ break; case FT_DIRECTORY: @@ -2067,7 +2074,10 @@ Already have correct symlink %s -> %s\n\ case FT_PLAINFILE: /* Call the retrieve loop. */ if (dlthis) - err = ftp_loop_internal (u, f, con, NULL, force_full_retrieve); + { + err = ftp_loop_internal (u, original_url, f, con, NULL, + force_full_retrieve); + } break; case FT_UNKNOWN: logprintf (LOG_NOTQUIET, _("%s: unknown/unsupported file type.\n"), @@ -2132,7 +2142,7 @@ Already have correct symlink %s -> %s\n\ /* 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, orig, con); + 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)); @@ -2145,7 +2155,8 @@ Already have correct symlink %s -> %s\n\ ftp_retrieve_glob on each directory entry. The function knows about excluded directories. */ static uerr_t -ftp_retrieve_dirs (struct url *u, struct fileinfo *f, ccon *con) +ftp_retrieve_dirs (struct url *u, struct url *original_url, + struct fileinfo *f, ccon *con) { char *container = NULL; int container_size = 0; @@ -2195,7 +2206,7 @@ Not descending to %s as it is excluded/n odir = xstrdup (u->dir); /* because url_set_dir will free u->dir. */ url_set_dir (u, newdir); - ftp_retrieve_glob (u, con, GLOB_GETALL); + ftp_retrieve_glob (u, original_url, con, GLOB_GETALL); url_set_dir (u, odir); xfree (odir); @@ -2254,14 +2265,15 @@ is_invalid_entry (struct fileinfo *f) GLOB_GLOBALL, use globbing; if it's GLOB_GETALL, download the whole directory. */ static uerr_t -ftp_retrieve_glob (struct url *u, ccon *con, int action) +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, con, &start); + res = ftp_get_listing (u, original_url, con, &start); if (res != RETROK) return res; /* First: weed out that do not conform the global rules given in @@ -2357,7 +2369,7 @@ ftp_retrieve_glob (struct url *u, ccon * if (start) { /* Just get everything. */ - res = ftp_retrieve_list (u, start, con); + res = ftp_retrieve_list (u, original_url, start, con); } else { @@ -2373,7 +2385,7 @@ ftp_retrieve_glob (struct url *u, ccon * { /* Let's try retrieving it anyway. */ con->st |= ON_YOUR_OWN; - res = ftp_loop_internal (u, NULL, con, NULL, false); + res = ftp_loop_internal (u, original_url, NULL, con, NULL, false); return res; } @@ -2393,8 +2405,8 @@ ftp_retrieve_glob (struct url *u, ccon * of URL. Inherently, its capabilities are limited on what can be encoded into a URL. */ uerr_t -ftp_loop (struct url *u, char **local_file, int *dt, struct url *proxy, - bool recursive, bool glob) +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; @@ -2415,16 +2427,17 @@ ftp_loop (struct url *u, char **local_fi if (!*u->file && !recursive) { struct fileinfo *f; - res = ftp_get_listing (u, &con, &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 (u, NULL))); + : url_file_name (url_file, NULL))); res = ftp_index (filename, u, f); if (res == FTPOK && opt.verbose) { @@ -2469,11 +2482,13 @@ ftp_loop (struct url *u, char **local_fi /* 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, &con, + res = ftp_retrieve_glob (u, original_url, &con, ispattern ? GLOB_GLOBALL : GLOB_GETONE); } else - res = ftp_loop_internal (u, NULL, &con, local_file, false); + { + res = ftp_loop_internal (u, original_url, NULL, &con, local_file, false); + } } if (res == FTPOK) res = RETROK; Index: wget-1.16.3/src/ftp.h =================================================================== --- wget-1.16.3.orig/src/ftp.h +++ wget-1.16.3/src/ftp.h @@ -150,7 +150,8 @@ enum wget_ftp_fstatus }; struct fileinfo *ftp_parse_ls (const char *, const enum stype); -uerr_t ftp_loop (struct url *, char **, int *, struct url *, bool, bool); +uerr_t ftp_loop (struct url *, struct url *, char **, int *, struct url *, + bool, bool); uerr_t ftp_index (const char *, struct url *, struct fileinfo *); Index: wget-1.16.3/src/retr.c =================================================================== --- wget-1.16.3.orig/src/retr.c +++ wget-1.16.3/src/retr.c @@ -807,7 +807,8 @@ retrieve_url (struct url * orig_parsed, if (redirection_count) oldrec = glob = false; - result = ftp_loop (u, &local_file, dt, proxy_url, recursive, glob); + result = ftp_loop (u, orig_parsed, &local_file, dt, proxy_url, + recursive, glob); recursive = oldrec; /* There is a possibility of having HTTP being redirected to