1*84d9c625SLionel Sambuc /* $NetBSD: fetch.c,v 1.205 2013/11/07 02:06:51 christos Exp $ */ 204203a83SThomas Cort 304203a83SThomas Cort /*- 404203a83SThomas Cort * Copyright (c) 1997-2009 The NetBSD Foundation, Inc. 504203a83SThomas Cort * All rights reserved. 604203a83SThomas Cort * 704203a83SThomas Cort * This code is derived from software contributed to The NetBSD Foundation 804203a83SThomas Cort * by Luke Mewburn. 904203a83SThomas Cort * 1004203a83SThomas Cort * This code is derived from software contributed to The NetBSD Foundation 1104203a83SThomas Cort * by Scott Aaron Bamford. 1204203a83SThomas Cort * 1304203a83SThomas Cort * Redistribution and use in source and binary forms, with or without 1404203a83SThomas Cort * modification, are permitted provided that the following conditions 1504203a83SThomas Cort * are met: 1604203a83SThomas Cort * 1. Redistributions of source code must retain the above copyright 1704203a83SThomas Cort * notice, this list of conditions and the following disclaimer. 1804203a83SThomas Cort * 2. Redistributions in binary form must reproduce the above copyright 1904203a83SThomas Cort * notice, this list of conditions and the following disclaimer in the 2004203a83SThomas Cort * documentation and/or other materials provided with the distribution. 2104203a83SThomas Cort * 2204203a83SThomas Cort * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS 2304203a83SThomas Cort * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED 2404203a83SThomas Cort * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 2504203a83SThomas Cort * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS 2604203a83SThomas Cort * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 2704203a83SThomas Cort * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 2804203a83SThomas Cort * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 2904203a83SThomas Cort * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 3004203a83SThomas Cort * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 3104203a83SThomas Cort * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 3204203a83SThomas Cort * POSSIBILITY OF SUCH DAMAGE. 3304203a83SThomas Cort */ 3404203a83SThomas Cort 3504203a83SThomas Cort #include <sys/cdefs.h> 3604203a83SThomas Cort #ifndef lint 37*84d9c625SLionel Sambuc __RCSID("$NetBSD: fetch.c,v 1.205 2013/11/07 02:06:51 christos Exp $"); 3804203a83SThomas Cort #endif /* not lint */ 3904203a83SThomas Cort 4004203a83SThomas Cort /* 4104203a83SThomas Cort * FTP User Program -- Command line file retrieval 4204203a83SThomas Cort */ 4304203a83SThomas Cort 4404203a83SThomas Cort #include <sys/types.h> 4504203a83SThomas Cort #include <sys/param.h> 4604203a83SThomas Cort #include <sys/socket.h> 4704203a83SThomas Cort #include <sys/stat.h> 4804203a83SThomas Cort #include <sys/time.h> 4904203a83SThomas Cort 5004203a83SThomas Cort #include <netinet/in.h> 5104203a83SThomas Cort 5204203a83SThomas Cort #include <arpa/ftp.h> 5304203a83SThomas Cort #include <arpa/inet.h> 5404203a83SThomas Cort 5504203a83SThomas Cort #include <assert.h> 5604203a83SThomas Cort #include <ctype.h> 5704203a83SThomas Cort #include <err.h> 5804203a83SThomas Cort #include <errno.h> 5904203a83SThomas Cort #include <netdb.h> 6004203a83SThomas Cort #include <fcntl.h> 6104203a83SThomas Cort #include <stdio.h> 6204203a83SThomas Cort #include <stdlib.h> 6304203a83SThomas Cort #include <string.h> 6404203a83SThomas Cort #include <unistd.h> 6504203a83SThomas Cort #include <time.h> 6604203a83SThomas Cort 6704203a83SThomas Cort #include "ssl.h" 6804203a83SThomas Cort #include "ftp_var.h" 6904203a83SThomas Cort #include "version.h" 7004203a83SThomas Cort 7104203a83SThomas Cort typedef enum { 7204203a83SThomas Cort UNKNOWN_URL_T=-1, 7304203a83SThomas Cort HTTP_URL_T, 7404203a83SThomas Cort #ifdef WITH_SSL 7504203a83SThomas Cort HTTPS_URL_T, 7604203a83SThomas Cort #endif 7704203a83SThomas Cort FTP_URL_T, 7804203a83SThomas Cort FILE_URL_T, 7904203a83SThomas Cort CLASSIC_URL_T 8004203a83SThomas Cort } url_t; 8104203a83SThomas Cort 8204203a83SThomas Cort __dead static void aborthttp(int); 83*84d9c625SLionel Sambuc __dead static void timeouthttp(int); 8404203a83SThomas Cort #ifndef NO_AUTH 8504203a83SThomas Cort static int auth_url(const char *, char **, const char *, const char *); 8604203a83SThomas Cort static void base64_encode(const unsigned char *, size_t, unsigned char *); 8704203a83SThomas Cort #endif 8804203a83SThomas Cort static int go_fetch(const char *); 8904203a83SThomas Cort static int fetch_ftp(const char *); 9004203a83SThomas Cort static int fetch_url(const char *, const char *, char *, char *); 9104203a83SThomas Cort static const char *match_token(const char **, const char *); 9204203a83SThomas Cort static int parse_url(const char *, const char *, url_t *, char **, 9304203a83SThomas Cort char **, char **, char **, in_port_t *, char **); 9404203a83SThomas Cort static void url_decode(char *); 9504203a83SThomas Cort 9604203a83SThomas Cort static int redirect_loop; 9704203a83SThomas Cort 9804203a83SThomas Cort 9904203a83SThomas Cort #define STRNEQUAL(a,b) (strncasecmp((a), (b), sizeof((b))-1) == 0) 10004203a83SThomas Cort #define ISLWS(x) ((x)=='\r' || (x)=='\n' || (x)==' ' || (x)=='\t') 10104203a83SThomas Cort #define SKIPLWS(x) do { while (ISLWS((*x))) x++; } while (0) 10204203a83SThomas Cort 10304203a83SThomas Cort 10404203a83SThomas Cort #define ABOUT_URL "about:" /* propaganda */ 10504203a83SThomas Cort #define FILE_URL "file://" /* file URL prefix */ 10604203a83SThomas Cort #define FTP_URL "ftp://" /* ftp URL prefix */ 10704203a83SThomas Cort #define HTTP_URL "http://" /* http URL prefix */ 10804203a83SThomas Cort #ifdef WITH_SSL 10904203a83SThomas Cort #define HTTPS_URL "https://" /* https URL prefix */ 11004203a83SThomas Cort 11104203a83SThomas Cort #define IS_HTTP_TYPE(urltype) \ 11204203a83SThomas Cort (((urltype) == HTTP_URL_T) || ((urltype) == HTTPS_URL_T)) 11304203a83SThomas Cort #else 11404203a83SThomas Cort #define IS_HTTP_TYPE(urltype) \ 11504203a83SThomas Cort ((urltype) == HTTP_URL_T) 11604203a83SThomas Cort #endif 11704203a83SThomas Cort 11804203a83SThomas Cort /* 11904203a83SThomas Cort * Determine if token is the next word in buf (case insensitive). 12004203a83SThomas Cort * If so, advance buf past the token and any trailing LWS, and 12104203a83SThomas Cort * return a pointer to the token (in buf). Otherwise, return NULL. 12204203a83SThomas Cort * token may be preceded by LWS. 12304203a83SThomas Cort * token must be followed by LWS or NUL. (I.e, don't partial match). 12404203a83SThomas Cort */ 12504203a83SThomas Cort static const char * 12604203a83SThomas Cort match_token(const char **buf, const char *token) 12704203a83SThomas Cort { 12804203a83SThomas Cort const char *p, *orig; 12904203a83SThomas Cort size_t tlen; 13004203a83SThomas Cort 13104203a83SThomas Cort tlen = strlen(token); 13204203a83SThomas Cort p = *buf; 13304203a83SThomas Cort SKIPLWS(p); 13404203a83SThomas Cort orig = p; 13504203a83SThomas Cort if (strncasecmp(p, token, tlen) != 0) 13604203a83SThomas Cort return NULL; 13704203a83SThomas Cort p += tlen; 13804203a83SThomas Cort if (*p != '\0' && !ISLWS(*p)) 13904203a83SThomas Cort return NULL; 14004203a83SThomas Cort SKIPLWS(p); 14104203a83SThomas Cort orig = *buf; 14204203a83SThomas Cort *buf = p; 14304203a83SThomas Cort return orig; 14404203a83SThomas Cort } 14504203a83SThomas Cort 14604203a83SThomas Cort #ifndef NO_AUTH 14704203a83SThomas Cort /* 14804203a83SThomas Cort * Generate authorization response based on given authentication challenge. 14904203a83SThomas Cort * Returns -1 if an error occurred, otherwise 0. 15004203a83SThomas Cort * Sets response to a malloc(3)ed string; caller should free. 15104203a83SThomas Cort */ 15204203a83SThomas Cort static int 15304203a83SThomas Cort auth_url(const char *challenge, char **response, const char *guser, 15404203a83SThomas Cort const char *gpass) 15504203a83SThomas Cort { 15604203a83SThomas Cort const char *cp, *scheme, *errormsg; 15704203a83SThomas Cort char *ep, *clear, *realm; 15804203a83SThomas Cort char uuser[BUFSIZ], *gotpass; 15904203a83SThomas Cort const char *upass; 16004203a83SThomas Cort int rval; 16104203a83SThomas Cort size_t len, clen, rlen; 16204203a83SThomas Cort 16304203a83SThomas Cort *response = NULL; 16404203a83SThomas Cort clear = realm = NULL; 16504203a83SThomas Cort rval = -1; 16604203a83SThomas Cort cp = challenge; 16704203a83SThomas Cort scheme = "Basic"; /* only support Basic authentication */ 16804203a83SThomas Cort gotpass = NULL; 16904203a83SThomas Cort 17004203a83SThomas Cort DPRINTF("auth_url: challenge `%s'\n", challenge); 17104203a83SThomas Cort 17204203a83SThomas Cort if (! match_token(&cp, scheme)) { 17304203a83SThomas Cort warnx("Unsupported authentication challenge `%s'", 17404203a83SThomas Cort challenge); 17504203a83SThomas Cort goto cleanup_auth_url; 17604203a83SThomas Cort } 17704203a83SThomas Cort 17804203a83SThomas Cort #define REALM "realm=\"" 17904203a83SThomas Cort if (STRNEQUAL(cp, REALM)) 18004203a83SThomas Cort cp += sizeof(REALM) - 1; 18104203a83SThomas Cort else { 18204203a83SThomas Cort warnx("Unsupported authentication challenge `%s'", 18304203a83SThomas Cort challenge); 18404203a83SThomas Cort goto cleanup_auth_url; 18504203a83SThomas Cort } 18604203a83SThomas Cort /* XXX: need to improve quoted-string parsing to support \ quoting, etc. */ 18704203a83SThomas Cort if ((ep = strchr(cp, '\"')) != NULL) { 18804203a83SThomas Cort len = ep - cp; 18904203a83SThomas Cort realm = (char *)ftp_malloc(len + 1); 19004203a83SThomas Cort (void)strlcpy(realm, cp, len + 1); 19104203a83SThomas Cort } else { 19204203a83SThomas Cort warnx("Unsupported authentication challenge `%s'", 19304203a83SThomas Cort challenge); 19404203a83SThomas Cort goto cleanup_auth_url; 19504203a83SThomas Cort } 19604203a83SThomas Cort 19704203a83SThomas Cort fprintf(ttyout, "Username for `%s': ", realm); 19804203a83SThomas Cort if (guser != NULL) { 19904203a83SThomas Cort (void)strlcpy(uuser, guser, sizeof(uuser)); 20004203a83SThomas Cort fprintf(ttyout, "%s\n", uuser); 20104203a83SThomas Cort } else { 20204203a83SThomas Cort (void)fflush(ttyout); 20304203a83SThomas Cort if (get_line(stdin, uuser, sizeof(uuser), &errormsg) < 0) { 20404203a83SThomas Cort warnx("%s; can't authenticate", errormsg); 20504203a83SThomas Cort goto cleanup_auth_url; 20604203a83SThomas Cort } 20704203a83SThomas Cort } 20804203a83SThomas Cort if (gpass != NULL) 20904203a83SThomas Cort upass = gpass; 21004203a83SThomas Cort else { 21104203a83SThomas Cort gotpass = getpass("Password: "); 21204203a83SThomas Cort if (gotpass == NULL) { 21304203a83SThomas Cort warnx("Can't read password"); 21404203a83SThomas Cort goto cleanup_auth_url; 21504203a83SThomas Cort } 21604203a83SThomas Cort upass = gotpass; 21704203a83SThomas Cort } 21804203a83SThomas Cort 21904203a83SThomas Cort clen = strlen(uuser) + strlen(upass) + 2; /* user + ":" + pass + "\0" */ 22004203a83SThomas Cort clear = (char *)ftp_malloc(clen); 22104203a83SThomas Cort (void)strlcpy(clear, uuser, clen); 22204203a83SThomas Cort (void)strlcat(clear, ":", clen); 22304203a83SThomas Cort (void)strlcat(clear, upass, clen); 22404203a83SThomas Cort if (gotpass) 22504203a83SThomas Cort memset(gotpass, 0, strlen(gotpass)); 22604203a83SThomas Cort 22704203a83SThomas Cort /* scheme + " " + enc + "\0" */ 22804203a83SThomas Cort rlen = strlen(scheme) + 1 + (clen + 2) * 4 / 3 + 1; 22904203a83SThomas Cort *response = (char *)ftp_malloc(rlen); 23004203a83SThomas Cort (void)strlcpy(*response, scheme, rlen); 23104203a83SThomas Cort len = strlcat(*response, " ", rlen); 23204203a83SThomas Cort /* use `clen - 1' to not encode the trailing NUL */ 23304203a83SThomas Cort base64_encode((unsigned char *)clear, clen - 1, 23404203a83SThomas Cort (unsigned char *)*response + len); 23504203a83SThomas Cort memset(clear, 0, clen); 23604203a83SThomas Cort rval = 0; 23704203a83SThomas Cort 23804203a83SThomas Cort cleanup_auth_url: 23904203a83SThomas Cort FREEPTR(clear); 24004203a83SThomas Cort FREEPTR(realm); 24104203a83SThomas Cort return (rval); 24204203a83SThomas Cort } 24304203a83SThomas Cort 24404203a83SThomas Cort /* 24504203a83SThomas Cort * Encode len bytes starting at clear using base64 encoding into encoded, 24604203a83SThomas Cort * which should be at least ((len + 2) * 4 / 3 + 1) in size. 24704203a83SThomas Cort */ 24804203a83SThomas Cort static void 24904203a83SThomas Cort base64_encode(const unsigned char *clear, size_t len, unsigned char *encoded) 25004203a83SThomas Cort { 25104203a83SThomas Cort static const unsigned char enc[] = 25204203a83SThomas Cort "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; 25304203a83SThomas Cort unsigned char *cp; 25404203a83SThomas Cort size_t i; 25504203a83SThomas Cort 25604203a83SThomas Cort cp = encoded; 25704203a83SThomas Cort for (i = 0; i < len; i += 3) { 25804203a83SThomas Cort *(cp++) = enc[((clear[i + 0] >> 2))]; 25904203a83SThomas Cort *(cp++) = enc[((clear[i + 0] << 4) & 0x30) 26004203a83SThomas Cort | ((clear[i + 1] >> 4) & 0x0f)]; 26104203a83SThomas Cort *(cp++) = enc[((clear[i + 1] << 2) & 0x3c) 26204203a83SThomas Cort | ((clear[i + 2] >> 6) & 0x03)]; 26304203a83SThomas Cort *(cp++) = enc[((clear[i + 2] ) & 0x3f)]; 26404203a83SThomas Cort } 26504203a83SThomas Cort *cp = '\0'; 26604203a83SThomas Cort while (i-- > len) 26704203a83SThomas Cort *(--cp) = '='; 26804203a83SThomas Cort } 26904203a83SThomas Cort #endif 27004203a83SThomas Cort 27104203a83SThomas Cort /* 27204203a83SThomas Cort * Decode %xx escapes in given string, `in-place'. 27304203a83SThomas Cort */ 27404203a83SThomas Cort static void 27504203a83SThomas Cort url_decode(char *url) 27604203a83SThomas Cort { 27704203a83SThomas Cort unsigned char *p, *q; 27804203a83SThomas Cort 27904203a83SThomas Cort if (EMPTYSTRING(url)) 28004203a83SThomas Cort return; 28104203a83SThomas Cort p = q = (unsigned char *)url; 28204203a83SThomas Cort 28304203a83SThomas Cort #define HEXTOINT(x) (x - (isdigit(x) ? '0' : (islower(x) ? 'a' : 'A') - 10)) 28404203a83SThomas Cort while (*p) { 28504203a83SThomas Cort if (p[0] == '%' 28604203a83SThomas Cort && p[1] && isxdigit((unsigned char)p[1]) 28704203a83SThomas Cort && p[2] && isxdigit((unsigned char)p[2])) { 28804203a83SThomas Cort *q++ = HEXTOINT(p[1]) * 16 + HEXTOINT(p[2]); 28904203a83SThomas Cort p+=3; 29004203a83SThomas Cort } else 29104203a83SThomas Cort *q++ = *p++; 29204203a83SThomas Cort } 29304203a83SThomas Cort *q = '\0'; 29404203a83SThomas Cort } 29504203a83SThomas Cort 29604203a83SThomas Cort 29704203a83SThomas Cort /* 29804203a83SThomas Cort * Parse URL of form (per RFC 3986): 29904203a83SThomas Cort * <type>://[<user>[:<password>]@]<host>[:<port>][/<path>] 30004203a83SThomas Cort * Returns -1 if a parse error occurred, otherwise 0. 30104203a83SThomas Cort * It's the caller's responsibility to url_decode() the returned 30204203a83SThomas Cort * user, pass and path. 30304203a83SThomas Cort * 30404203a83SThomas Cort * Sets type to url_t, each of the given char ** pointers to a 30504203a83SThomas Cort * malloc(3)ed strings of the relevant section, and port to 30604203a83SThomas Cort * the number given, or ftpport if ftp://, or httpport if http://. 30704203a83SThomas Cort * 30804203a83SThomas Cort * XXX: this is not totally RFC 3986 compliant; <path> will have the 30904203a83SThomas Cort * leading `/' unless it's an ftp:// URL, as this makes things easier 31004203a83SThomas Cort * for file:// and http:// URLs. ftp:// URLs have the `/' between the 31104203a83SThomas Cort * host and the URL-path removed, but any additional leading slashes 31204203a83SThomas Cort * in the URL-path are retained (because they imply that we should 31304203a83SThomas Cort * later do "CWD" with a null argument). 31404203a83SThomas Cort * 31504203a83SThomas Cort * Examples: 31604203a83SThomas Cort * input URL output path 31704203a83SThomas Cort * --------- ----------- 31804203a83SThomas Cort * "http://host" "/" 31904203a83SThomas Cort * "http://host/" "/" 32004203a83SThomas Cort * "http://host/path" "/path" 32104203a83SThomas Cort * "file://host/dir/file" "dir/file" 32204203a83SThomas Cort * "ftp://host" "" 32304203a83SThomas Cort * "ftp://host/" "" 32404203a83SThomas Cort * "ftp://host//" "/" 32504203a83SThomas Cort * "ftp://host/dir/file" "dir/file" 32604203a83SThomas Cort * "ftp://host//dir/file" "/dir/file" 32704203a83SThomas Cort */ 32804203a83SThomas Cort static int 32904203a83SThomas Cort parse_url(const char *url, const char *desc, url_t *utype, 33004203a83SThomas Cort char **uuser, char **pass, char **host, char **port, 33104203a83SThomas Cort in_port_t *portnum, char **path) 33204203a83SThomas Cort { 33304203a83SThomas Cort const char *origurl, *tport; 33404203a83SThomas Cort char *cp, *ep, *thost; 33504203a83SThomas Cort size_t len; 33604203a83SThomas Cort 33704203a83SThomas Cort if (url == NULL || desc == NULL || utype == NULL || uuser == NULL 33804203a83SThomas Cort || pass == NULL || host == NULL || port == NULL || portnum == NULL 33904203a83SThomas Cort || path == NULL) 34004203a83SThomas Cort errx(1, "parse_url: invoked with NULL argument!"); 34104203a83SThomas Cort DPRINTF("parse_url: %s `%s'\n", desc, url); 34204203a83SThomas Cort 34304203a83SThomas Cort origurl = url; 34404203a83SThomas Cort *utype = UNKNOWN_URL_T; 34504203a83SThomas Cort *uuser = *pass = *host = *port = *path = NULL; 34604203a83SThomas Cort *portnum = 0; 34704203a83SThomas Cort tport = NULL; 34804203a83SThomas Cort 34904203a83SThomas Cort if (STRNEQUAL(url, HTTP_URL)) { 35004203a83SThomas Cort url += sizeof(HTTP_URL) - 1; 35104203a83SThomas Cort *utype = HTTP_URL_T; 35204203a83SThomas Cort *portnum = HTTP_PORT; 35304203a83SThomas Cort tport = httpport; 35404203a83SThomas Cort } else if (STRNEQUAL(url, FTP_URL)) { 35504203a83SThomas Cort url += sizeof(FTP_URL) - 1; 35604203a83SThomas Cort *utype = FTP_URL_T; 35704203a83SThomas Cort *portnum = FTP_PORT; 35804203a83SThomas Cort tport = ftpport; 35904203a83SThomas Cort } else if (STRNEQUAL(url, FILE_URL)) { 36004203a83SThomas Cort url += sizeof(FILE_URL) - 1; 36104203a83SThomas Cort *utype = FILE_URL_T; 36204203a83SThomas Cort #ifdef WITH_SSL 36304203a83SThomas Cort } else if (STRNEQUAL(url, HTTPS_URL)) { 36404203a83SThomas Cort url += sizeof(HTTPS_URL) - 1; 36504203a83SThomas Cort *utype = HTTPS_URL_T; 36604203a83SThomas Cort *portnum = HTTPS_PORT; 36704203a83SThomas Cort tport = httpsport; 36804203a83SThomas Cort #endif 36904203a83SThomas Cort } else { 37004203a83SThomas Cort warnx("Invalid %s `%s'", desc, url); 37104203a83SThomas Cort cleanup_parse_url: 37204203a83SThomas Cort FREEPTR(*uuser); 37304203a83SThomas Cort if (*pass != NULL) 37404203a83SThomas Cort memset(*pass, 0, strlen(*pass)); 37504203a83SThomas Cort FREEPTR(*pass); 37604203a83SThomas Cort FREEPTR(*host); 37704203a83SThomas Cort FREEPTR(*port); 37804203a83SThomas Cort FREEPTR(*path); 37904203a83SThomas Cort return (-1); 38004203a83SThomas Cort } 38104203a83SThomas Cort 38204203a83SThomas Cort if (*url == '\0') 38304203a83SThomas Cort return (0); 38404203a83SThomas Cort 38504203a83SThomas Cort /* find [user[:pass]@]host[:port] */ 38604203a83SThomas Cort ep = strchr(url, '/'); 38704203a83SThomas Cort if (ep == NULL) 38804203a83SThomas Cort thost = ftp_strdup(url); 38904203a83SThomas Cort else { 39004203a83SThomas Cort len = ep - url; 39104203a83SThomas Cort thost = (char *)ftp_malloc(len + 1); 39204203a83SThomas Cort (void)strlcpy(thost, url, len + 1); 39304203a83SThomas Cort if (*utype == FTP_URL_T) /* skip first / for ftp URLs */ 39404203a83SThomas Cort ep++; 39504203a83SThomas Cort *path = ftp_strdup(ep); 39604203a83SThomas Cort } 39704203a83SThomas Cort 39804203a83SThomas Cort cp = strchr(thost, '@'); /* look for user[:pass]@ in URLs */ 39904203a83SThomas Cort if (cp != NULL) { 40004203a83SThomas Cort if (*utype == FTP_URL_T) 40104203a83SThomas Cort anonftp = 0; /* disable anonftp */ 40204203a83SThomas Cort *uuser = thost; 40304203a83SThomas Cort *cp = '\0'; 40404203a83SThomas Cort thost = ftp_strdup(cp + 1); 40504203a83SThomas Cort cp = strchr(*uuser, ':'); 40604203a83SThomas Cort if (cp != NULL) { 40704203a83SThomas Cort *cp = '\0'; 40804203a83SThomas Cort *pass = ftp_strdup(cp + 1); 40904203a83SThomas Cort } 41004203a83SThomas Cort url_decode(*uuser); 41104203a83SThomas Cort if (*pass) 41204203a83SThomas Cort url_decode(*pass); 41304203a83SThomas Cort } 41404203a83SThomas Cort 41504203a83SThomas Cort #ifdef INET6 41604203a83SThomas Cort /* 41704203a83SThomas Cort * Check if thost is an encoded IPv6 address, as per 41804203a83SThomas Cort * RFC 3986: 41904203a83SThomas Cort * `[' ipv6-address ']' 42004203a83SThomas Cort */ 42104203a83SThomas Cort if (*thost == '[') { 42204203a83SThomas Cort cp = thost + 1; 42304203a83SThomas Cort if ((ep = strchr(cp, ']')) == NULL || 42404203a83SThomas Cort (ep[1] != '\0' && ep[1] != ':')) { 42504203a83SThomas Cort warnx("Invalid address `%s' in %s `%s'", 42604203a83SThomas Cort thost, desc, origurl); 42704203a83SThomas Cort goto cleanup_parse_url; 42804203a83SThomas Cort } 42904203a83SThomas Cort len = ep - cp; /* change `[xyz]' -> `xyz' */ 43004203a83SThomas Cort memmove(thost, thost + 1, len); 43104203a83SThomas Cort thost[len] = '\0'; 43204203a83SThomas Cort if (! isipv6addr(thost)) { 43304203a83SThomas Cort warnx("Invalid IPv6 address `%s' in %s `%s'", 43404203a83SThomas Cort thost, desc, origurl); 43504203a83SThomas Cort goto cleanup_parse_url; 43604203a83SThomas Cort } 43704203a83SThomas Cort cp = ep + 1; 43804203a83SThomas Cort if (*cp == ':') 43904203a83SThomas Cort cp++; 44004203a83SThomas Cort else 44104203a83SThomas Cort cp = NULL; 44204203a83SThomas Cort } else 44304203a83SThomas Cort #endif /* INET6 */ 44404203a83SThomas Cort if ((cp = strchr(thost, ':')) != NULL) 44504203a83SThomas Cort *cp++ = '\0'; 44604203a83SThomas Cort *host = thost; 44704203a83SThomas Cort 44804203a83SThomas Cort /* look for [:port] */ 44904203a83SThomas Cort if (cp != NULL) { 45004203a83SThomas Cort unsigned long nport; 45104203a83SThomas Cort 45204203a83SThomas Cort nport = strtoul(cp, &ep, 10); 45304203a83SThomas Cort if (*cp == '\0' || *ep != '\0' || 45404203a83SThomas Cort nport < 1 || nport > MAX_IN_PORT_T) { 45504203a83SThomas Cort warnx("Unknown port `%s' in %s `%s'", 45604203a83SThomas Cort cp, desc, origurl); 45704203a83SThomas Cort goto cleanup_parse_url; 45804203a83SThomas Cort } 45904203a83SThomas Cort *portnum = nport; 46004203a83SThomas Cort tport = cp; 46104203a83SThomas Cort } 46204203a83SThomas Cort 46304203a83SThomas Cort if (tport != NULL) 46404203a83SThomas Cort *port = ftp_strdup(tport); 46504203a83SThomas Cort if (*path == NULL) { 46604203a83SThomas Cort const char *emptypath = "/"; 46704203a83SThomas Cort if (*utype == FTP_URL_T) /* skip first / for ftp URLs */ 46804203a83SThomas Cort emptypath++; 46904203a83SThomas Cort *path = ftp_strdup(emptypath); 47004203a83SThomas Cort } 47104203a83SThomas Cort 47204203a83SThomas Cort DPRINTF("parse_url: user `%s' pass `%s' host %s port %s(%d) " 47304203a83SThomas Cort "path `%s'\n", 47404203a83SThomas Cort STRorNULL(*uuser), STRorNULL(*pass), 47504203a83SThomas Cort STRorNULL(*host), STRorNULL(*port), 47604203a83SThomas Cort *portnum ? *portnum : -1, STRorNULL(*path)); 47704203a83SThomas Cort 47804203a83SThomas Cort return (0); 47904203a83SThomas Cort } 48004203a83SThomas Cort 48104203a83SThomas Cort sigjmp_buf httpabort; 48204203a83SThomas Cort 48304203a83SThomas Cort /* 48404203a83SThomas Cort * Retrieve URL, via a proxy if necessary, using HTTP. 48504203a83SThomas Cort * If proxyenv is set, use that for the proxy, otherwise try ftp_proxy or 48604203a83SThomas Cort * http_proxy/https_proxy as appropriate. 48704203a83SThomas Cort * Supports HTTP redirects. 48804203a83SThomas Cort * Returns 1 on failure, 0 on completed xfer, -1 if ftp connection 48904203a83SThomas Cort * is still open (e.g, ftp xfer with trailing /) 49004203a83SThomas Cort */ 49104203a83SThomas Cort static int 49204203a83SThomas Cort fetch_url(const char *url, const char *proxyenv, char *proxyauth, char *wwwauth) 49304203a83SThomas Cort { 49404203a83SThomas Cort struct addrinfo hints, *res, *res0 = NULL; 49504203a83SThomas Cort int error; 496*84d9c625SLionel Sambuc sigfunc volatile oldint; 497*84d9c625SLionel Sambuc sigfunc volatile oldpipe; 498*84d9c625SLionel Sambuc sigfunc volatile oldalrm; 499*84d9c625SLionel Sambuc sigfunc volatile oldquit; 50004203a83SThomas Cort int volatile s; 50104203a83SThomas Cort struct stat sb; 50204203a83SThomas Cort int volatile ischunked; 50304203a83SThomas Cort int volatile isproxy; 50404203a83SThomas Cort int volatile rval; 50504203a83SThomas Cort int volatile hcode; 50604203a83SThomas Cort int len; 50704203a83SThomas Cort size_t flen; 50804203a83SThomas Cort static size_t bufsize; 50904203a83SThomas Cort static char *xferbuf; 51004203a83SThomas Cort const char *cp, *token; 51104203a83SThomas Cort char *ep; 51204203a83SThomas Cort char buf[FTPBUFLEN]; 51304203a83SThomas Cort const char *errormsg; 51404203a83SThomas Cort char *volatile savefile; 51504203a83SThomas Cort char *volatile auth; 51604203a83SThomas Cort char *volatile location; 51704203a83SThomas Cort char *volatile message; 51804203a83SThomas Cort char *uuser, *pass, *host, *port, *path; 51904203a83SThomas Cort char *volatile decodedpath; 52004203a83SThomas Cort char *puser, *ppass, *useragent; 52104203a83SThomas Cort off_t hashbytes, rangestart, rangeend, entitylen; 52204203a83SThomas Cort int (*volatile closefunc)(FILE *); 52304203a83SThomas Cort FETCH *volatile fin; 52404203a83SThomas Cort FILE *volatile fout; 525*84d9c625SLionel Sambuc const char *volatile penv = proxyenv; 52604203a83SThomas Cort time_t mtime; 52704203a83SThomas Cort url_t urltype; 52804203a83SThomas Cort in_port_t portnum; 52904203a83SThomas Cort #ifdef WITH_SSL 53004203a83SThomas Cort void *ssl; 53104203a83SThomas Cort #endif 53204203a83SThomas Cort 533*84d9c625SLionel Sambuc DPRINTF("%s: `%s' proxyenv `%s'\n", __func__, url, STRorNULL(penv)); 53404203a83SThomas Cort 535*84d9c625SLionel Sambuc oldquit = oldalrm = oldint = oldpipe = NULL; 53604203a83SThomas Cort closefunc = NULL; 53704203a83SThomas Cort fin = NULL; 53804203a83SThomas Cort fout = NULL; 53904203a83SThomas Cort s = -1; 54004203a83SThomas Cort savefile = NULL; 54104203a83SThomas Cort auth = location = message = NULL; 54204203a83SThomas Cort ischunked = isproxy = hcode = 0; 54304203a83SThomas Cort rval = 1; 54404203a83SThomas Cort uuser = pass = host = path = decodedpath = puser = ppass = NULL; 54504203a83SThomas Cort 546*84d9c625SLionel Sambuc if (sigsetjmp(httpabort, 1)) 547*84d9c625SLionel Sambuc goto cleanup_fetch_url; 548*84d9c625SLionel Sambuc 54904203a83SThomas Cort if (parse_url(url, "URL", &urltype, &uuser, &pass, &host, &port, 55004203a83SThomas Cort &portnum, &path) == -1) 55104203a83SThomas Cort goto cleanup_fetch_url; 55204203a83SThomas Cort 55304203a83SThomas Cort if (urltype == FILE_URL_T && ! EMPTYSTRING(host) 55404203a83SThomas Cort && strcasecmp(host, "localhost") != 0) { 55504203a83SThomas Cort warnx("No support for non local file URL `%s'", url); 55604203a83SThomas Cort goto cleanup_fetch_url; 55704203a83SThomas Cort } 55804203a83SThomas Cort 55904203a83SThomas Cort if (EMPTYSTRING(path)) { 56004203a83SThomas Cort if (urltype == FTP_URL_T) { 56104203a83SThomas Cort rval = fetch_ftp(url); 56204203a83SThomas Cort goto cleanup_fetch_url; 56304203a83SThomas Cort } 56404203a83SThomas Cort if (!IS_HTTP_TYPE(urltype) || outfile == NULL) { 56504203a83SThomas Cort warnx("Invalid URL (no file after host) `%s'", url); 56604203a83SThomas Cort goto cleanup_fetch_url; 56704203a83SThomas Cort } 56804203a83SThomas Cort } 56904203a83SThomas Cort 57004203a83SThomas Cort decodedpath = ftp_strdup(path); 57104203a83SThomas Cort url_decode(decodedpath); 57204203a83SThomas Cort 57304203a83SThomas Cort if (outfile) 57404203a83SThomas Cort savefile = ftp_strdup(outfile); 57504203a83SThomas Cort else { 57604203a83SThomas Cort cp = strrchr(decodedpath, '/'); /* find savefile */ 57704203a83SThomas Cort if (cp != NULL) 57804203a83SThomas Cort savefile = ftp_strdup(cp + 1); 57904203a83SThomas Cort else 58004203a83SThomas Cort savefile = ftp_strdup(decodedpath); 58104203a83SThomas Cort } 582*84d9c625SLionel Sambuc DPRINTF("%s: savefile `%s'\n", __func__, savefile); 58304203a83SThomas Cort if (EMPTYSTRING(savefile)) { 58404203a83SThomas Cort if (urltype == FTP_URL_T) { 58504203a83SThomas Cort rval = fetch_ftp(url); 58604203a83SThomas Cort goto cleanup_fetch_url; 58704203a83SThomas Cort } 58804203a83SThomas Cort warnx("No file after directory (you must specify an " 58904203a83SThomas Cort "output file) `%s'", url); 59004203a83SThomas Cort goto cleanup_fetch_url; 59104203a83SThomas Cort } 59204203a83SThomas Cort 59304203a83SThomas Cort restart_point = 0; 59404203a83SThomas Cort filesize = -1; 59504203a83SThomas Cort rangestart = rangeend = entitylen = -1; 59604203a83SThomas Cort mtime = -1; 59704203a83SThomas Cort if (restartautofetch) { 59804203a83SThomas Cort if (strcmp(savefile, "-") != 0 && *savefile != '|' && 59904203a83SThomas Cort stat(savefile, &sb) == 0) 60004203a83SThomas Cort restart_point = sb.st_size; 60104203a83SThomas Cort } 60204203a83SThomas Cort if (urltype == FILE_URL_T) { /* file:// URLs */ 60304203a83SThomas Cort direction = "copied"; 60404203a83SThomas Cort fin = fetch_open(decodedpath, "r"); 60504203a83SThomas Cort if (fin == NULL) { 60604203a83SThomas Cort warn("Can't open `%s'", decodedpath); 60704203a83SThomas Cort goto cleanup_fetch_url; 60804203a83SThomas Cort } 60904203a83SThomas Cort if (fstat(fetch_fileno(fin), &sb) == 0) { 61004203a83SThomas Cort mtime = sb.st_mtime; 61104203a83SThomas Cort filesize = sb.st_size; 61204203a83SThomas Cort } 61304203a83SThomas Cort if (restart_point) { 61404203a83SThomas Cort if (lseek(fetch_fileno(fin), restart_point, SEEK_SET) < 0) { 61504203a83SThomas Cort warn("Can't seek to restart `%s'", 61604203a83SThomas Cort decodedpath); 61704203a83SThomas Cort goto cleanup_fetch_url; 61804203a83SThomas Cort } 61904203a83SThomas Cort } 62004203a83SThomas Cort if (verbose) { 62104203a83SThomas Cort fprintf(ttyout, "Copying %s", decodedpath); 62204203a83SThomas Cort if (restart_point) 62304203a83SThomas Cort fprintf(ttyout, " (restarting at " LLF ")", 62404203a83SThomas Cort (LLT)restart_point); 62504203a83SThomas Cort fputs("\n", ttyout); 62604203a83SThomas Cort } 62704203a83SThomas Cort if (0 == rcvbuf_size) { 62804203a83SThomas Cort rcvbuf_size = 8 * 1024; /* XXX */ 62904203a83SThomas Cort } 63004203a83SThomas Cort } else { /* ftp:// or http:// URLs */ 63104203a83SThomas Cort const char *leading; 63204203a83SThomas Cort int hasleading; 63304203a83SThomas Cort 634*84d9c625SLionel Sambuc if (penv == NULL) { 63504203a83SThomas Cort #ifdef WITH_SSL 63604203a83SThomas Cort if (urltype == HTTPS_URL_T) 637*84d9c625SLionel Sambuc penv = getoptionvalue("https_proxy"); 63804203a83SThomas Cort #endif 639*84d9c625SLionel Sambuc if (penv == NULL && IS_HTTP_TYPE(urltype)) 640*84d9c625SLionel Sambuc penv = getoptionvalue("http_proxy"); 64104203a83SThomas Cort else if (urltype == FTP_URL_T) 642*84d9c625SLionel Sambuc penv = getoptionvalue("ftp_proxy"); 64304203a83SThomas Cort } 64404203a83SThomas Cort direction = "retrieved"; 645*84d9c625SLionel Sambuc if (! EMPTYSTRING(penv)) { /* use proxy */ 64604203a83SThomas Cort url_t purltype; 64704203a83SThomas Cort char *phost, *ppath; 64804203a83SThomas Cort char *pport, *no_proxy; 64904203a83SThomas Cort in_port_t pportnum; 65004203a83SThomas Cort 65104203a83SThomas Cort isproxy = 1; 65204203a83SThomas Cort 65304203a83SThomas Cort /* check URL against list of no_proxied sites */ 65404203a83SThomas Cort no_proxy = getoptionvalue("no_proxy"); 65504203a83SThomas Cort if (! EMPTYSTRING(no_proxy)) { 65604203a83SThomas Cort char *np, *np_copy, *np_iter; 65704203a83SThomas Cort unsigned long np_port; 65804203a83SThomas Cort size_t hlen, plen; 65904203a83SThomas Cort 66004203a83SThomas Cort np_iter = np_copy = ftp_strdup(no_proxy); 66104203a83SThomas Cort hlen = strlen(host); 66204203a83SThomas Cort while ((cp = strsep(&np_iter, " ,")) != NULL) { 66304203a83SThomas Cort if (*cp == '\0') 66404203a83SThomas Cort continue; 66504203a83SThomas Cort if ((np = strrchr(cp, ':')) != NULL) { 66604203a83SThomas Cort *np++ = '\0'; 66704203a83SThomas Cort np_port = strtoul(np, &ep, 10); 66804203a83SThomas Cort if (*np == '\0' || *ep != '\0') 66904203a83SThomas Cort continue; 67004203a83SThomas Cort if (np_port != portnum) 67104203a83SThomas Cort continue; 67204203a83SThomas Cort } 67304203a83SThomas Cort plen = strlen(cp); 67404203a83SThomas Cort if (hlen < plen) 67504203a83SThomas Cort continue; 67604203a83SThomas Cort if (strncasecmp(host + hlen - plen, 67704203a83SThomas Cort cp, plen) == 0) { 67804203a83SThomas Cort isproxy = 0; 67904203a83SThomas Cort break; 68004203a83SThomas Cort } 68104203a83SThomas Cort } 68204203a83SThomas Cort FREEPTR(np_copy); 68304203a83SThomas Cort if (isproxy == 0 && urltype == FTP_URL_T) { 68404203a83SThomas Cort rval = fetch_ftp(url); 68504203a83SThomas Cort goto cleanup_fetch_url; 68604203a83SThomas Cort } 68704203a83SThomas Cort } 68804203a83SThomas Cort 68904203a83SThomas Cort if (isproxy) { 69004203a83SThomas Cort if (restart_point) { 69104203a83SThomas Cort warnx("Can't restart via proxy URL `%s'", 692*84d9c625SLionel Sambuc penv); 69304203a83SThomas Cort goto cleanup_fetch_url; 69404203a83SThomas Cort } 695*84d9c625SLionel Sambuc if (parse_url(penv, "proxy URL", &purltype, 69604203a83SThomas Cort &puser, &ppass, &phost, &pport, &pportnum, 69704203a83SThomas Cort &ppath) == -1) 69804203a83SThomas Cort goto cleanup_fetch_url; 69904203a83SThomas Cort 70004203a83SThomas Cort if ((!IS_HTTP_TYPE(purltype) 70104203a83SThomas Cort && purltype != FTP_URL_T) || 70204203a83SThomas Cort EMPTYSTRING(phost) || 70304203a83SThomas Cort (! EMPTYSTRING(ppath) 70404203a83SThomas Cort && strcmp(ppath, "/") != 0)) { 705*84d9c625SLionel Sambuc warnx("Malformed proxy URL `%s'", penv); 70604203a83SThomas Cort FREEPTR(phost); 70704203a83SThomas Cort FREEPTR(pport); 70804203a83SThomas Cort FREEPTR(ppath); 70904203a83SThomas Cort goto cleanup_fetch_url; 71004203a83SThomas Cort } 71104203a83SThomas Cort if (isipv6addr(host) && 71204203a83SThomas Cort strchr(host, '%') != NULL) { 71304203a83SThomas Cort warnx( 71404203a83SThomas Cort "Scoped address notation `%s' disallowed via web proxy", 71504203a83SThomas Cort host); 71604203a83SThomas Cort FREEPTR(phost); 71704203a83SThomas Cort FREEPTR(pport); 71804203a83SThomas Cort FREEPTR(ppath); 71904203a83SThomas Cort goto cleanup_fetch_url; 72004203a83SThomas Cort } 72104203a83SThomas Cort 72204203a83SThomas Cort FREEPTR(host); 72304203a83SThomas Cort host = phost; 72404203a83SThomas Cort FREEPTR(port); 72504203a83SThomas Cort port = pport; 72604203a83SThomas Cort FREEPTR(path); 72704203a83SThomas Cort path = ftp_strdup(url); 72804203a83SThomas Cort FREEPTR(ppath); 72904203a83SThomas Cort urltype = purltype; 73004203a83SThomas Cort } 731*84d9c625SLionel Sambuc } /* ! EMPTYSTRING(penv) */ 73204203a83SThomas Cort 73304203a83SThomas Cort memset(&hints, 0, sizeof(hints)); 73404203a83SThomas Cort hints.ai_flags = 0; 73504203a83SThomas Cort hints.ai_family = family; 73604203a83SThomas Cort hints.ai_socktype = SOCK_STREAM; 73704203a83SThomas Cort hints.ai_protocol = 0; 73804203a83SThomas Cort error = getaddrinfo(host, port, &hints, &res0); 73904203a83SThomas Cort if (error) { 74004203a83SThomas Cort warnx("Can't LOOKUP `%s:%s': %s", host, port, 74104203a83SThomas Cort (error == EAI_SYSTEM) ? strerror(errno) 74204203a83SThomas Cort : gai_strerror(error)); 74304203a83SThomas Cort goto cleanup_fetch_url; 74404203a83SThomas Cort } 74504203a83SThomas Cort if (res0->ai_canonname) 74604203a83SThomas Cort host = res0->ai_canonname; 74704203a83SThomas Cort 74804203a83SThomas Cort s = -1; 74904203a83SThomas Cort #ifdef WITH_SSL 75004203a83SThomas Cort ssl = NULL; 75104203a83SThomas Cort #endif 75204203a83SThomas Cort for (res = res0; res; res = res->ai_next) { 75304203a83SThomas Cort char hname[NI_MAXHOST], sname[NI_MAXSERV]; 75404203a83SThomas Cort 75504203a83SThomas Cort ai_unmapped(res); 75604203a83SThomas Cort if (getnameinfo(res->ai_addr, res->ai_addrlen, 75704203a83SThomas Cort hname, sizeof(hname), sname, sizeof(sname), 75804203a83SThomas Cort NI_NUMERICHOST | NI_NUMERICSERV) != 0) { 75904203a83SThomas Cort strlcpy(hname, "?", sizeof(hname)); 76004203a83SThomas Cort strlcpy(sname, "?", sizeof(sname)); 76104203a83SThomas Cort } 76204203a83SThomas Cort 76304203a83SThomas Cort if (verbose && res0->ai_next) { 76404203a83SThomas Cort fprintf(ttyout, "Trying %s:%s ...\n", 76504203a83SThomas Cort hname, sname); 76604203a83SThomas Cort } 76704203a83SThomas Cort 76804203a83SThomas Cort s = socket(res->ai_family, SOCK_STREAM, 76904203a83SThomas Cort res->ai_protocol); 77004203a83SThomas Cort if (s < 0) { 77104203a83SThomas Cort warn( 77204203a83SThomas Cort "Can't create socket for connection to " 77304203a83SThomas Cort "`%s:%s'", hname, sname); 77404203a83SThomas Cort continue; 77504203a83SThomas Cort } 77604203a83SThomas Cort 77704203a83SThomas Cort if (ftp_connect(s, res->ai_addr, res->ai_addrlen, 77804203a83SThomas Cort verbose || !res->ai_next) < 0) { 77904203a83SThomas Cort close(s); 78004203a83SThomas Cort s = -1; 78104203a83SThomas Cort continue; 78204203a83SThomas Cort } 78304203a83SThomas Cort 78404203a83SThomas Cort #ifdef WITH_SSL 78504203a83SThomas Cort if (urltype == HTTPS_URL_T) { 78604203a83SThomas Cort if ((ssl = fetch_start_ssl(s)) == NULL) { 78704203a83SThomas Cort close(s); 78804203a83SThomas Cort s = -1; 78904203a83SThomas Cort continue; 79004203a83SThomas Cort } 79104203a83SThomas Cort } 79204203a83SThomas Cort #endif 79304203a83SThomas Cort 79404203a83SThomas Cort /* success */ 79504203a83SThomas Cort break; 79604203a83SThomas Cort } 79704203a83SThomas Cort 79804203a83SThomas Cort if (s < 0) { 79904203a83SThomas Cort warnx("Can't connect to `%s:%s'", host, port); 80004203a83SThomas Cort goto cleanup_fetch_url; 80104203a83SThomas Cort } 80204203a83SThomas Cort 803*84d9c625SLionel Sambuc oldalrm = xsignal(SIGALRM, timeouthttp); 804*84d9c625SLionel Sambuc alarmtimer(quit_time ? quit_time : 60); 80504203a83SThomas Cort fin = fetch_fdopen(s, "r+"); 80604203a83SThomas Cort fetch_set_ssl(fin, ssl); 807*84d9c625SLionel Sambuc alarmtimer(0); 80804203a83SThomas Cort 809*84d9c625SLionel Sambuc alarmtimer(quit_time ? quit_time : 60); 81004203a83SThomas Cort /* 81104203a83SThomas Cort * Construct and send the request. 81204203a83SThomas Cort */ 81304203a83SThomas Cort if (verbose) 81404203a83SThomas Cort fprintf(ttyout, "Requesting %s\n", url); 81504203a83SThomas Cort leading = " ("; 81604203a83SThomas Cort hasleading = 0; 81704203a83SThomas Cort if (isproxy) { 81804203a83SThomas Cort if (verbose) { 81904203a83SThomas Cort fprintf(ttyout, "%svia %s:%s", leading, 82004203a83SThomas Cort host, port); 82104203a83SThomas Cort leading = ", "; 82204203a83SThomas Cort hasleading++; 82304203a83SThomas Cort } 82404203a83SThomas Cort fetch_printf(fin, "GET %s HTTP/1.0\r\n", path); 82504203a83SThomas Cort if (flushcache) 82604203a83SThomas Cort fetch_printf(fin, "Pragma: no-cache\r\n"); 82704203a83SThomas Cort } else { 82804203a83SThomas Cort fetch_printf(fin, "GET %s HTTP/1.1\r\n", path); 82904203a83SThomas Cort if (strchr(host, ':')) { 83004203a83SThomas Cort char *h, *p; 83104203a83SThomas Cort 83204203a83SThomas Cort /* 83304203a83SThomas Cort * strip off IPv6 scope identifier, since it is 83404203a83SThomas Cort * local to the node 83504203a83SThomas Cort */ 83604203a83SThomas Cort h = ftp_strdup(host); 83704203a83SThomas Cort if (isipv6addr(h) && 83804203a83SThomas Cort (p = strchr(h, '%')) != NULL) { 83904203a83SThomas Cort *p = '\0'; 84004203a83SThomas Cort } 84104203a83SThomas Cort fetch_printf(fin, "Host: [%s]", h); 84204203a83SThomas Cort free(h); 84304203a83SThomas Cort } else 84404203a83SThomas Cort fetch_printf(fin, "Host: %s", host); 84504203a83SThomas Cort #ifdef WITH_SSL 84604203a83SThomas Cort if ((urltype == HTTP_URL_T && portnum != HTTP_PORT) || 84704203a83SThomas Cort (urltype == HTTPS_URL_T && portnum != HTTPS_PORT)) 84804203a83SThomas Cort #else 84904203a83SThomas Cort if (portnum != HTTP_PORT) 85004203a83SThomas Cort #endif 85104203a83SThomas Cort fetch_printf(fin, ":%u", portnum); 85204203a83SThomas Cort fetch_printf(fin, "\r\n"); 85304203a83SThomas Cort fetch_printf(fin, "Accept: */*\r\n"); 85404203a83SThomas Cort fetch_printf(fin, "Connection: close\r\n"); 85504203a83SThomas Cort if (restart_point) { 85604203a83SThomas Cort fputs(leading, ttyout); 85704203a83SThomas Cort fetch_printf(fin, "Range: bytes=" LLF "-\r\n", 85804203a83SThomas Cort (LLT)restart_point); 85904203a83SThomas Cort fprintf(ttyout, "restarting at " LLF, 86004203a83SThomas Cort (LLT)restart_point); 86104203a83SThomas Cort leading = ", "; 86204203a83SThomas Cort hasleading++; 86304203a83SThomas Cort } 86404203a83SThomas Cort if (flushcache) 86504203a83SThomas Cort fetch_printf(fin, "Cache-Control: no-cache\r\n"); 86604203a83SThomas Cort } 86704203a83SThomas Cort if ((useragent=getenv("FTPUSERAGENT")) != NULL) { 86804203a83SThomas Cort fetch_printf(fin, "User-Agent: %s\r\n", useragent); 86904203a83SThomas Cort } else { 87004203a83SThomas Cort fetch_printf(fin, "User-Agent: %s/%s\r\n", 87104203a83SThomas Cort FTP_PRODUCT, FTP_VERSION); 87204203a83SThomas Cort } 87304203a83SThomas Cort if (wwwauth) { 87404203a83SThomas Cort if (verbose) { 87504203a83SThomas Cort fprintf(ttyout, "%swith authorization", 87604203a83SThomas Cort leading); 87704203a83SThomas Cort leading = ", "; 87804203a83SThomas Cort hasleading++; 87904203a83SThomas Cort } 88004203a83SThomas Cort fetch_printf(fin, "Authorization: %s\r\n", wwwauth); 88104203a83SThomas Cort } 88204203a83SThomas Cort if (proxyauth) { 88304203a83SThomas Cort if (verbose) { 88404203a83SThomas Cort fprintf(ttyout, 88504203a83SThomas Cort "%swith proxy authorization", leading); 88604203a83SThomas Cort leading = ", "; 88704203a83SThomas Cort hasleading++; 88804203a83SThomas Cort } 88904203a83SThomas Cort fetch_printf(fin, "Proxy-Authorization: %s\r\n", proxyauth); 89004203a83SThomas Cort } 89104203a83SThomas Cort if (verbose && hasleading) 89204203a83SThomas Cort fputs(")\n", ttyout); 89304203a83SThomas Cort fetch_printf(fin, "\r\n"); 89404203a83SThomas Cort if (fetch_flush(fin) == EOF) { 89504203a83SThomas Cort warn("Writing HTTP request"); 896*84d9c625SLionel Sambuc alarmtimer(0); 89704203a83SThomas Cort goto cleanup_fetch_url; 89804203a83SThomas Cort } 899*84d9c625SLionel Sambuc alarmtimer(0); 90004203a83SThomas Cort 90104203a83SThomas Cort /* Read the response */ 902*84d9c625SLionel Sambuc alarmtimer(quit_time ? quit_time : 60); 90304203a83SThomas Cort len = fetch_getline(fin, buf, sizeof(buf), &errormsg); 904*84d9c625SLionel Sambuc alarmtimer(0); 90504203a83SThomas Cort if (len < 0) { 90604203a83SThomas Cort if (*errormsg == '\n') 90704203a83SThomas Cort errormsg++; 90804203a83SThomas Cort warnx("Receiving HTTP reply: %s", errormsg); 90904203a83SThomas Cort goto cleanup_fetch_url; 91004203a83SThomas Cort } 91104203a83SThomas Cort while (len > 0 && (ISLWS(buf[len-1]))) 91204203a83SThomas Cort buf[--len] = '\0'; 913*84d9c625SLionel Sambuc DPRINTF("%s: received `%s'\n", __func__, buf); 91404203a83SThomas Cort 91504203a83SThomas Cort /* Determine HTTP response code */ 91604203a83SThomas Cort cp = strchr(buf, ' '); 91704203a83SThomas Cort if (cp == NULL) 91804203a83SThomas Cort goto improper; 91904203a83SThomas Cort else 92004203a83SThomas Cort cp++; 92104203a83SThomas Cort hcode = strtol(cp, &ep, 10); 92204203a83SThomas Cort if (*ep != '\0' && !isspace((unsigned char)*ep)) 92304203a83SThomas Cort goto improper; 92404203a83SThomas Cort message = ftp_strdup(cp); 92504203a83SThomas Cort 92604203a83SThomas Cort /* Read the rest of the header. */ 92704203a83SThomas Cort while (1) { 928*84d9c625SLionel Sambuc alarmtimer(quit_time ? quit_time : 60); 92904203a83SThomas Cort len = fetch_getline(fin, buf, sizeof(buf), &errormsg); 930*84d9c625SLionel Sambuc alarmtimer(0); 93104203a83SThomas Cort if (len < 0) { 93204203a83SThomas Cort if (*errormsg == '\n') 93304203a83SThomas Cort errormsg++; 93404203a83SThomas Cort warnx("Receiving HTTP reply: %s", errormsg); 93504203a83SThomas Cort goto cleanup_fetch_url; 93604203a83SThomas Cort } 93704203a83SThomas Cort while (len > 0 && (ISLWS(buf[len-1]))) 93804203a83SThomas Cort buf[--len] = '\0'; 93904203a83SThomas Cort if (len == 0) 94004203a83SThomas Cort break; 941*84d9c625SLionel Sambuc DPRINTF("%s: received `%s'\n", __func__, buf); 94204203a83SThomas Cort 94304203a83SThomas Cort /* 94404203a83SThomas Cort * Look for some headers 94504203a83SThomas Cort */ 94604203a83SThomas Cort 94704203a83SThomas Cort cp = buf; 94804203a83SThomas Cort 94904203a83SThomas Cort if (match_token(&cp, "Content-Length:")) { 95004203a83SThomas Cort filesize = STRTOLL(cp, &ep, 10); 95104203a83SThomas Cort if (filesize < 0 || *ep != '\0') 95204203a83SThomas Cort goto improper; 953*84d9c625SLionel Sambuc DPRINTF("%s: parsed len as: " LLF "\n", 954*84d9c625SLionel Sambuc __func__, (LLT)filesize); 95504203a83SThomas Cort 95604203a83SThomas Cort } else if (match_token(&cp, "Content-Range:")) { 95704203a83SThomas Cort if (! match_token(&cp, "bytes")) 95804203a83SThomas Cort goto improper; 95904203a83SThomas Cort 96004203a83SThomas Cort if (*cp == '*') 96104203a83SThomas Cort cp++; 96204203a83SThomas Cort else { 96304203a83SThomas Cort rangestart = STRTOLL(cp, &ep, 10); 96404203a83SThomas Cort if (rangestart < 0 || *ep != '-') 96504203a83SThomas Cort goto improper; 96604203a83SThomas Cort cp = ep + 1; 96704203a83SThomas Cort rangeend = STRTOLL(cp, &ep, 10); 96804203a83SThomas Cort if (rangeend < 0 || rangeend < rangestart) 96904203a83SThomas Cort goto improper; 97004203a83SThomas Cort cp = ep; 97104203a83SThomas Cort } 97204203a83SThomas Cort if (*cp != '/') 97304203a83SThomas Cort goto improper; 97404203a83SThomas Cort cp++; 97504203a83SThomas Cort if (*cp == '*') 97604203a83SThomas Cort cp++; 97704203a83SThomas Cort else { 97804203a83SThomas Cort entitylen = STRTOLL(cp, &ep, 10); 97904203a83SThomas Cort if (entitylen < 0) 98004203a83SThomas Cort goto improper; 98104203a83SThomas Cort cp = ep; 98204203a83SThomas Cort } 98304203a83SThomas Cort if (*cp != '\0') 98404203a83SThomas Cort goto improper; 98504203a83SThomas Cort 98604203a83SThomas Cort #ifndef NO_DEBUG 98704203a83SThomas Cort if (ftp_debug) { 98804203a83SThomas Cort fprintf(ttyout, "parsed range as: "); 98904203a83SThomas Cort if (rangestart == -1) 99004203a83SThomas Cort fprintf(ttyout, "*"); 99104203a83SThomas Cort else 99204203a83SThomas Cort fprintf(ttyout, LLF "-" LLF, 99304203a83SThomas Cort (LLT)rangestart, 99404203a83SThomas Cort (LLT)rangeend); 99504203a83SThomas Cort fprintf(ttyout, "/" LLF "\n", (LLT)entitylen); 99604203a83SThomas Cort } 99704203a83SThomas Cort #endif 99804203a83SThomas Cort if (! restart_point) { 99904203a83SThomas Cort warnx( 100004203a83SThomas Cort "Received unexpected Content-Range header"); 100104203a83SThomas Cort goto cleanup_fetch_url; 100204203a83SThomas Cort } 100304203a83SThomas Cort 100404203a83SThomas Cort } else if (match_token(&cp, "Last-Modified:")) { 100504203a83SThomas Cort struct tm parsed; 100604203a83SThomas Cort const char *t; 100704203a83SThomas Cort 100804203a83SThomas Cort memset(&parsed, 0, sizeof(parsed)); 100904203a83SThomas Cort t = parse_rfc2616time(&parsed, cp); 101004203a83SThomas Cort if (t != NULL) { 101104203a83SThomas Cort parsed.tm_isdst = -1; 101204203a83SThomas Cort if (*t == '\0') 101304203a83SThomas Cort mtime = timegm(&parsed); 101404203a83SThomas Cort #ifndef NO_DEBUG 101504203a83SThomas Cort if (ftp_debug && mtime != -1) { 101604203a83SThomas Cort fprintf(ttyout, 101704203a83SThomas Cort "parsed time as: %s", 101804203a83SThomas Cort rfc2822time(localtime(&mtime))); 101904203a83SThomas Cort } 102004203a83SThomas Cort #endif 102104203a83SThomas Cort } 102204203a83SThomas Cort 102304203a83SThomas Cort } else if (match_token(&cp, "Location:")) { 102404203a83SThomas Cort location = ftp_strdup(cp); 1025*84d9c625SLionel Sambuc DPRINTF("%s: parsed location as `%s'\n", 1026*84d9c625SLionel Sambuc __func__, cp); 102704203a83SThomas Cort 102804203a83SThomas Cort } else if (match_token(&cp, "Transfer-Encoding:")) { 102904203a83SThomas Cort if (match_token(&cp, "binary")) { 103004203a83SThomas Cort warnx( 103104203a83SThomas Cort "Bogus transfer encoding `binary' (fetching anyway)"); 103204203a83SThomas Cort continue; 103304203a83SThomas Cort } 103404203a83SThomas Cort if (! (token = match_token(&cp, "chunked"))) { 103504203a83SThomas Cort warnx( 103604203a83SThomas Cort "Unsupported transfer encoding `%s'", 103704203a83SThomas Cort token); 103804203a83SThomas Cort goto cleanup_fetch_url; 103904203a83SThomas Cort } 104004203a83SThomas Cort ischunked++; 1041*84d9c625SLionel Sambuc DPRINTF("%s: using chunked encoding\n", 1042*84d9c625SLionel Sambuc __func__); 104304203a83SThomas Cort 104404203a83SThomas Cort } else if (match_token(&cp, "Proxy-Authenticate:") 104504203a83SThomas Cort || match_token(&cp, "WWW-Authenticate:")) { 104604203a83SThomas Cort if (! (token = match_token(&cp, "Basic"))) { 1047*84d9c625SLionel Sambuc DPRINTF("%s: skipping unknown auth " 1048*84d9c625SLionel Sambuc "scheme `%s'\n", __func__, token); 104904203a83SThomas Cort continue; 105004203a83SThomas Cort } 105104203a83SThomas Cort FREEPTR(auth); 105204203a83SThomas Cort auth = ftp_strdup(token); 1053*84d9c625SLionel Sambuc DPRINTF("%s: parsed auth as `%s'\n", 1054*84d9c625SLionel Sambuc __func__, cp); 105504203a83SThomas Cort } 105604203a83SThomas Cort 105704203a83SThomas Cort } 105804203a83SThomas Cort /* finished parsing header */ 105904203a83SThomas Cort 106004203a83SThomas Cort switch (hcode) { 106104203a83SThomas Cort case 200: 106204203a83SThomas Cort break; 106304203a83SThomas Cort case 206: 106404203a83SThomas Cort if (! restart_point) { 106504203a83SThomas Cort warnx("Not expecting partial content header"); 106604203a83SThomas Cort goto cleanup_fetch_url; 106704203a83SThomas Cort } 106804203a83SThomas Cort break; 106904203a83SThomas Cort case 300: 107004203a83SThomas Cort case 301: 107104203a83SThomas Cort case 302: 107204203a83SThomas Cort case 303: 107304203a83SThomas Cort case 305: 107404203a83SThomas Cort case 307: 107504203a83SThomas Cort if (EMPTYSTRING(location)) { 107604203a83SThomas Cort warnx( 107704203a83SThomas Cort "No redirection Location provided by server"); 107804203a83SThomas Cort goto cleanup_fetch_url; 107904203a83SThomas Cort } 108004203a83SThomas Cort if (redirect_loop++ > 5) { 108104203a83SThomas Cort warnx("Too many redirections requested"); 108204203a83SThomas Cort goto cleanup_fetch_url; 108304203a83SThomas Cort } 108404203a83SThomas Cort if (hcode == 305) { 108504203a83SThomas Cort if (verbose) 108604203a83SThomas Cort fprintf(ttyout, "Redirected via %s\n", 108704203a83SThomas Cort location); 108804203a83SThomas Cort rval = fetch_url(url, location, 108904203a83SThomas Cort proxyauth, wwwauth); 109004203a83SThomas Cort } else { 109104203a83SThomas Cort if (verbose) 109204203a83SThomas Cort fprintf(ttyout, "Redirected to %s\n", 109304203a83SThomas Cort location); 109404203a83SThomas Cort rval = go_fetch(location); 109504203a83SThomas Cort } 109604203a83SThomas Cort goto cleanup_fetch_url; 109704203a83SThomas Cort #ifndef NO_AUTH 109804203a83SThomas Cort case 401: 109904203a83SThomas Cort case 407: 110004203a83SThomas Cort { 110104203a83SThomas Cort char **authp; 110204203a83SThomas Cort char *auser, *apass; 110304203a83SThomas Cort 110404203a83SThomas Cort if (hcode == 401) { 110504203a83SThomas Cort authp = &wwwauth; 110604203a83SThomas Cort auser = uuser; 110704203a83SThomas Cort apass = pass; 110804203a83SThomas Cort } else { 110904203a83SThomas Cort authp = &proxyauth; 111004203a83SThomas Cort auser = puser; 111104203a83SThomas Cort apass = ppass; 111204203a83SThomas Cort } 111304203a83SThomas Cort if (verbose || *authp == NULL || 111404203a83SThomas Cort auser == NULL || apass == NULL) 111504203a83SThomas Cort fprintf(ttyout, "%s\n", message); 111604203a83SThomas Cort if (EMPTYSTRING(auth)) { 111704203a83SThomas Cort warnx( 111804203a83SThomas Cort "No authentication challenge provided by server"); 111904203a83SThomas Cort goto cleanup_fetch_url; 112004203a83SThomas Cort } 112104203a83SThomas Cort if (*authp != NULL) { 112204203a83SThomas Cort char reply[10]; 112304203a83SThomas Cort 112404203a83SThomas Cort fprintf(ttyout, 112504203a83SThomas Cort "Authorization failed. Retry (y/n)? "); 112604203a83SThomas Cort if (get_line(stdin, reply, sizeof(reply), NULL) 112704203a83SThomas Cort < 0) { 112804203a83SThomas Cort goto cleanup_fetch_url; 112904203a83SThomas Cort } 113004203a83SThomas Cort if (tolower((unsigned char)reply[0]) != 'y') 113104203a83SThomas Cort goto cleanup_fetch_url; 113204203a83SThomas Cort auser = NULL; 113304203a83SThomas Cort apass = NULL; 113404203a83SThomas Cort } 113504203a83SThomas Cort if (auth_url(auth, authp, auser, apass) == 0) { 1136*84d9c625SLionel Sambuc rval = fetch_url(url, penv, 113704203a83SThomas Cort proxyauth, wwwauth); 113804203a83SThomas Cort memset(*authp, 0, strlen(*authp)); 113904203a83SThomas Cort FREEPTR(*authp); 114004203a83SThomas Cort } 114104203a83SThomas Cort goto cleanup_fetch_url; 114204203a83SThomas Cort } 114304203a83SThomas Cort #endif 114404203a83SThomas Cort default: 114504203a83SThomas Cort if (message) 114604203a83SThomas Cort warnx("Error retrieving file `%s'", message); 114704203a83SThomas Cort else 114804203a83SThomas Cort warnx("Unknown error retrieving file"); 114904203a83SThomas Cort goto cleanup_fetch_url; 115004203a83SThomas Cort } 115104203a83SThomas Cort } /* end of ftp:// or http:// specific setup */ 115204203a83SThomas Cort 115304203a83SThomas Cort /* Open the output file. */ 115404203a83SThomas Cort if (strcmp(savefile, "-") == 0) { 115504203a83SThomas Cort fout = stdout; 115604203a83SThomas Cort } else if (*savefile == '|') { 1157*84d9c625SLionel Sambuc oldpipe = xsignal(SIGPIPE, SIG_IGN); 115804203a83SThomas Cort fout = popen(savefile + 1, "w"); 115904203a83SThomas Cort if (fout == NULL) { 116004203a83SThomas Cort warn("Can't execute `%s'", savefile + 1); 116104203a83SThomas Cort goto cleanup_fetch_url; 116204203a83SThomas Cort } 116304203a83SThomas Cort closefunc = pclose; 116404203a83SThomas Cort } else { 116504203a83SThomas Cort if ((rangeend != -1 && rangeend <= restart_point) || 116604203a83SThomas Cort (rangestart == -1 && filesize != -1 && filesize <= restart_point)) { 116704203a83SThomas Cort /* already done */ 116804203a83SThomas Cort if (verbose) 116904203a83SThomas Cort fprintf(ttyout, "already done\n"); 117004203a83SThomas Cort rval = 0; 117104203a83SThomas Cort goto cleanup_fetch_url; 117204203a83SThomas Cort } 117304203a83SThomas Cort if (restart_point && rangestart != -1) { 117404203a83SThomas Cort if (entitylen != -1) 117504203a83SThomas Cort filesize = entitylen; 117604203a83SThomas Cort if (rangestart != restart_point) { 117704203a83SThomas Cort warnx( 117804203a83SThomas Cort "Size of `%s' differs from save file `%s'", 117904203a83SThomas Cort url, savefile); 118004203a83SThomas Cort goto cleanup_fetch_url; 118104203a83SThomas Cort } 118204203a83SThomas Cort fout = fopen(savefile, "a"); 118304203a83SThomas Cort } else 118404203a83SThomas Cort fout = fopen(savefile, "w"); 118504203a83SThomas Cort if (fout == NULL) { 118604203a83SThomas Cort warn("Can't open `%s'", savefile); 118704203a83SThomas Cort goto cleanup_fetch_url; 118804203a83SThomas Cort } 118904203a83SThomas Cort closefunc = fclose; 119004203a83SThomas Cort } 119104203a83SThomas Cort 119204203a83SThomas Cort /* Trap signals */ 1193*84d9c625SLionel Sambuc oldquit = xsignal(SIGQUIT, psummary); 1194*84d9c625SLionel Sambuc oldint = xsignal(SIGINT, aborthttp); 119504203a83SThomas Cort 119604203a83SThomas Cort assert(rcvbuf_size > 0); 119704203a83SThomas Cort if ((size_t)rcvbuf_size > bufsize) { 119804203a83SThomas Cort if (xferbuf) 119904203a83SThomas Cort (void)free(xferbuf); 120004203a83SThomas Cort bufsize = rcvbuf_size; 120104203a83SThomas Cort xferbuf = ftp_malloc(bufsize); 120204203a83SThomas Cort } 120304203a83SThomas Cort 120404203a83SThomas Cort bytes = 0; 120504203a83SThomas Cort hashbytes = mark; 1206*84d9c625SLionel Sambuc if (oldalrm) { 1207*84d9c625SLionel Sambuc (void)xsignal(SIGALRM, oldalrm); 1208*84d9c625SLionel Sambuc oldalrm = NULL; 1209*84d9c625SLionel Sambuc } 121004203a83SThomas Cort progressmeter(-1); 121104203a83SThomas Cort 121204203a83SThomas Cort /* Finally, suck down the file. */ 121304203a83SThomas Cort do { 121404203a83SThomas Cort long chunksize; 121504203a83SThomas Cort short lastchunk; 121604203a83SThomas Cort 121704203a83SThomas Cort chunksize = 0; 121804203a83SThomas Cort lastchunk = 0; 121904203a83SThomas Cort /* read chunk-size */ 122004203a83SThomas Cort if (ischunked) { 122104203a83SThomas Cort if (fetch_getln(xferbuf, bufsize, fin) == NULL) { 122204203a83SThomas Cort warnx("Unexpected EOF reading chunk-size"); 122304203a83SThomas Cort goto cleanup_fetch_url; 122404203a83SThomas Cort } 122504203a83SThomas Cort errno = 0; 122604203a83SThomas Cort chunksize = strtol(xferbuf, &ep, 16); 122704203a83SThomas Cort if (ep == xferbuf) { 122804203a83SThomas Cort warnx("Invalid chunk-size"); 122904203a83SThomas Cort goto cleanup_fetch_url; 123004203a83SThomas Cort } 123104203a83SThomas Cort if (errno == ERANGE || chunksize < 0) { 123204203a83SThomas Cort errno = ERANGE; 123304203a83SThomas Cort warn("Chunk-size `%.*s'", 123404203a83SThomas Cort (int)(ep-xferbuf), xferbuf); 123504203a83SThomas Cort goto cleanup_fetch_url; 123604203a83SThomas Cort } 123704203a83SThomas Cort 123804203a83SThomas Cort /* 123904203a83SThomas Cort * XXX: Work around bug in Apache 1.3.9 and 124004203a83SThomas Cort * 1.3.11, which incorrectly put trailing 124104203a83SThomas Cort * space after the chunk-size. 124204203a83SThomas Cort */ 124304203a83SThomas Cort while (*ep == ' ') 124404203a83SThomas Cort ep++; 124504203a83SThomas Cort 124604203a83SThomas Cort /* skip [ chunk-ext ] */ 124704203a83SThomas Cort if (*ep == ';') { 124804203a83SThomas Cort while (*ep && *ep != '\r') 124904203a83SThomas Cort ep++; 125004203a83SThomas Cort } 125104203a83SThomas Cort 125204203a83SThomas Cort if (strcmp(ep, "\r\n") != 0) { 125304203a83SThomas Cort warnx("Unexpected data following chunk-size"); 125404203a83SThomas Cort goto cleanup_fetch_url; 125504203a83SThomas Cort } 1256*84d9c625SLionel Sambuc DPRINTF("%s: got chunk-size of " LLF "\n", __func__, 125704203a83SThomas Cort (LLT)chunksize); 125804203a83SThomas Cort if (chunksize == 0) { 125904203a83SThomas Cort lastchunk = 1; 126004203a83SThomas Cort goto chunkdone; 126104203a83SThomas Cort } 126204203a83SThomas Cort } 126304203a83SThomas Cort /* transfer file or chunk */ 126404203a83SThomas Cort while (1) { 126504203a83SThomas Cort struct timeval then, now, td; 1266*84d9c625SLionel Sambuc volatile off_t bufrem; 126704203a83SThomas Cort 126804203a83SThomas Cort if (rate_get) 126904203a83SThomas Cort (void)gettimeofday(&then, NULL); 127004203a83SThomas Cort bufrem = rate_get ? rate_get : (off_t)bufsize; 127104203a83SThomas Cort if (ischunked) 127204203a83SThomas Cort bufrem = MIN(chunksize, bufrem); 127304203a83SThomas Cort while (bufrem > 0) { 127404203a83SThomas Cort flen = fetch_read(xferbuf, sizeof(char), 127504203a83SThomas Cort MIN((off_t)bufsize, bufrem), fin); 127604203a83SThomas Cort if (flen <= 0) 127704203a83SThomas Cort goto chunkdone; 127804203a83SThomas Cort bytes += flen; 127904203a83SThomas Cort bufrem -= flen; 128004203a83SThomas Cort if (fwrite(xferbuf, sizeof(char), flen, fout) 128104203a83SThomas Cort != flen) { 128204203a83SThomas Cort warn("Writing `%s'", savefile); 128304203a83SThomas Cort goto cleanup_fetch_url; 128404203a83SThomas Cort } 128504203a83SThomas Cort if (hash && !progress) { 128604203a83SThomas Cort while (bytes >= hashbytes) { 128704203a83SThomas Cort (void)putc('#', ttyout); 128804203a83SThomas Cort hashbytes += mark; 128904203a83SThomas Cort } 129004203a83SThomas Cort (void)fflush(ttyout); 129104203a83SThomas Cort } 129204203a83SThomas Cort if (ischunked) { 129304203a83SThomas Cort chunksize -= flen; 129404203a83SThomas Cort if (chunksize <= 0) 129504203a83SThomas Cort break; 129604203a83SThomas Cort } 129704203a83SThomas Cort } 129804203a83SThomas Cort if (rate_get) { 129904203a83SThomas Cort while (1) { 130004203a83SThomas Cort (void)gettimeofday(&now, NULL); 130104203a83SThomas Cort timersub(&now, &then, &td); 130204203a83SThomas Cort if (td.tv_sec > 0) 130304203a83SThomas Cort break; 130404203a83SThomas Cort usleep(1000000 - td.tv_usec); 130504203a83SThomas Cort } 130604203a83SThomas Cort } 130704203a83SThomas Cort if (ischunked && chunksize <= 0) 130804203a83SThomas Cort break; 130904203a83SThomas Cort } 131004203a83SThomas Cort /* read CRLF after chunk*/ 131104203a83SThomas Cort chunkdone: 131204203a83SThomas Cort if (ischunked) { 131304203a83SThomas Cort if (fetch_getln(xferbuf, bufsize, fin) == NULL) { 1314*84d9c625SLionel Sambuc alarmtimer(0); 131504203a83SThomas Cort warnx("Unexpected EOF reading chunk CRLF"); 131604203a83SThomas Cort goto cleanup_fetch_url; 131704203a83SThomas Cort } 131804203a83SThomas Cort if (strcmp(xferbuf, "\r\n") != 0) { 131904203a83SThomas Cort warnx("Unexpected data following chunk"); 132004203a83SThomas Cort goto cleanup_fetch_url; 132104203a83SThomas Cort } 132204203a83SThomas Cort if (lastchunk) 132304203a83SThomas Cort break; 132404203a83SThomas Cort } 132504203a83SThomas Cort } while (ischunked); 132604203a83SThomas Cort 132704203a83SThomas Cort /* XXX: deal with optional trailer & CRLF here? */ 132804203a83SThomas Cort 132904203a83SThomas Cort if (hash && !progress && bytes > 0) { 133004203a83SThomas Cort if (bytes < mark) 133104203a83SThomas Cort (void)putc('#', ttyout); 133204203a83SThomas Cort (void)putc('\n', ttyout); 133304203a83SThomas Cort } 133404203a83SThomas Cort if (fetch_error(fin)) { 133504203a83SThomas Cort warn("Reading file"); 133604203a83SThomas Cort goto cleanup_fetch_url; 133704203a83SThomas Cort } 133804203a83SThomas Cort progressmeter(1); 133904203a83SThomas Cort (void)fflush(fout); 134004203a83SThomas Cort if (closefunc == fclose && mtime != -1) { 134104203a83SThomas Cort struct timeval tval[2]; 134204203a83SThomas Cort 134304203a83SThomas Cort (void)gettimeofday(&tval[0], NULL); 134404203a83SThomas Cort tval[1].tv_sec = mtime; 134504203a83SThomas Cort tval[1].tv_usec = 0; 134604203a83SThomas Cort (*closefunc)(fout); 134704203a83SThomas Cort fout = NULL; 134804203a83SThomas Cort 134904203a83SThomas Cort if (utimes(savefile, tval) == -1) { 135004203a83SThomas Cort fprintf(ttyout, 135104203a83SThomas Cort "Can't change modification time to %s", 135204203a83SThomas Cort rfc2822time(localtime(&mtime))); 135304203a83SThomas Cort } 135404203a83SThomas Cort } 135504203a83SThomas Cort if (bytes > 0) 135604203a83SThomas Cort ptransfer(0); 135704203a83SThomas Cort bytes = 0; 135804203a83SThomas Cort 135904203a83SThomas Cort rval = 0; 136004203a83SThomas Cort goto cleanup_fetch_url; 136104203a83SThomas Cort 136204203a83SThomas Cort improper: 136304203a83SThomas Cort warnx("Improper response from `%s:%s'", host, port); 136404203a83SThomas Cort 136504203a83SThomas Cort cleanup_fetch_url: 1366*84d9c625SLionel Sambuc if (oldint) 1367*84d9c625SLionel Sambuc (void)xsignal(SIGINT, oldint); 1368*84d9c625SLionel Sambuc if (oldpipe) 1369*84d9c625SLionel Sambuc (void)xsignal(SIGPIPE, oldpipe); 1370*84d9c625SLionel Sambuc if (oldalrm) 1371*84d9c625SLionel Sambuc (void)xsignal(SIGALRM, oldalrm); 1372*84d9c625SLionel Sambuc if (oldquit) 1373*84d9c625SLionel Sambuc (void)xsignal(SIGQUIT, oldpipe); 137404203a83SThomas Cort if (fin != NULL) 137504203a83SThomas Cort fetch_close(fin); 137604203a83SThomas Cort else if (s != -1) 137704203a83SThomas Cort close(s); 137804203a83SThomas Cort if (closefunc != NULL && fout != NULL) 137904203a83SThomas Cort (*closefunc)(fout); 138004203a83SThomas Cort if (res0) 138104203a83SThomas Cort freeaddrinfo(res0); 138204203a83SThomas Cort FREEPTR(savefile); 138304203a83SThomas Cort FREEPTR(uuser); 138404203a83SThomas Cort if (pass != NULL) 138504203a83SThomas Cort memset(pass, 0, strlen(pass)); 138604203a83SThomas Cort FREEPTR(pass); 138704203a83SThomas Cort FREEPTR(host); 138804203a83SThomas Cort FREEPTR(port); 138904203a83SThomas Cort FREEPTR(path); 139004203a83SThomas Cort FREEPTR(decodedpath); 139104203a83SThomas Cort FREEPTR(puser); 139204203a83SThomas Cort if (ppass != NULL) 139304203a83SThomas Cort memset(ppass, 0, strlen(ppass)); 139404203a83SThomas Cort FREEPTR(ppass); 139504203a83SThomas Cort FREEPTR(auth); 139604203a83SThomas Cort FREEPTR(location); 139704203a83SThomas Cort FREEPTR(message); 139804203a83SThomas Cort return (rval); 139904203a83SThomas Cort } 140004203a83SThomas Cort 140104203a83SThomas Cort /* 140204203a83SThomas Cort * Abort a HTTP retrieval 140304203a83SThomas Cort */ 140404203a83SThomas Cort static void 140504203a83SThomas Cort aborthttp(int notused) 140604203a83SThomas Cort { 140704203a83SThomas Cort char msgbuf[100]; 1408*84d9c625SLionel Sambuc int len; 140904203a83SThomas Cort 141004203a83SThomas Cort sigint_raised = 1; 141104203a83SThomas Cort alarmtimer(0); 1412*84d9c625SLionel Sambuc if (fromatty) { 1413*84d9c625SLionel Sambuc len = snprintf(msgbuf, sizeof(msgbuf), 1414*84d9c625SLionel Sambuc "\n%s: HTTP fetch aborted.\n", getprogname()); 1415*84d9c625SLionel Sambuc if (len > 0) 141604203a83SThomas Cort write(fileno(ttyout), msgbuf, len); 1417*84d9c625SLionel Sambuc } 1418*84d9c625SLionel Sambuc siglongjmp(httpabort, 1); 1419*84d9c625SLionel Sambuc } 1420*84d9c625SLionel Sambuc 1421*84d9c625SLionel Sambuc static void 1422*84d9c625SLionel Sambuc timeouthttp(int notused) 1423*84d9c625SLionel Sambuc { 1424*84d9c625SLionel Sambuc char msgbuf[100]; 1425*84d9c625SLionel Sambuc int len; 1426*84d9c625SLionel Sambuc 1427*84d9c625SLionel Sambuc alarmtimer(0); 1428*84d9c625SLionel Sambuc if (fromatty) { 1429*84d9c625SLionel Sambuc len = snprintf(msgbuf, sizeof(msgbuf), 1430*84d9c625SLionel Sambuc "\n%s: HTTP fetch timeout.\n", getprogname()); 1431*84d9c625SLionel Sambuc if (len > 0) 1432*84d9c625SLionel Sambuc write(fileno(ttyout), msgbuf, len); 1433*84d9c625SLionel Sambuc } 143404203a83SThomas Cort siglongjmp(httpabort, 1); 143504203a83SThomas Cort } 143604203a83SThomas Cort 143704203a83SThomas Cort /* 143804203a83SThomas Cort * Retrieve ftp URL or classic ftp argument using FTP. 143904203a83SThomas Cort * Returns 1 on failure, 0 on completed xfer, -1 if ftp connection 144004203a83SThomas Cort * is still open (e.g, ftp xfer with trailing /) 144104203a83SThomas Cort */ 144204203a83SThomas Cort static int 144304203a83SThomas Cort fetch_ftp(const char *url) 144404203a83SThomas Cort { 144504203a83SThomas Cort char *cp, *xargv[5], rempath[MAXPATHLEN]; 144604203a83SThomas Cort char *host, *path, *dir, *file, *uuser, *pass; 144704203a83SThomas Cort char *port; 144804203a83SThomas Cort char cmdbuf[MAXPATHLEN]; 144904203a83SThomas Cort char dirbuf[4]; 145004203a83SThomas Cort int dirhasglob, filehasglob, rval, transtype, xargc; 145104203a83SThomas Cort int oanonftp, oautologin; 145204203a83SThomas Cort in_port_t portnum; 145304203a83SThomas Cort url_t urltype; 145404203a83SThomas Cort 145504203a83SThomas Cort DPRINTF("fetch_ftp: `%s'\n", url); 145604203a83SThomas Cort host = path = dir = file = uuser = pass = NULL; 145704203a83SThomas Cort port = NULL; 145804203a83SThomas Cort rval = 1; 145904203a83SThomas Cort transtype = TYPE_I; 146004203a83SThomas Cort 146104203a83SThomas Cort if (STRNEQUAL(url, FTP_URL)) { 146204203a83SThomas Cort if ((parse_url(url, "URL", &urltype, &uuser, &pass, 146304203a83SThomas Cort &host, &port, &portnum, &path) == -1) || 146404203a83SThomas Cort (uuser != NULL && *uuser == '\0') || 146504203a83SThomas Cort EMPTYSTRING(host)) { 146604203a83SThomas Cort warnx("Invalid URL `%s'", url); 146704203a83SThomas Cort goto cleanup_fetch_ftp; 146804203a83SThomas Cort } 146904203a83SThomas Cort /* 147004203a83SThomas Cort * Note: Don't url_decode(path) here. We need to keep the 147104203a83SThomas Cort * distinction between "/" and "%2F" until later. 147204203a83SThomas Cort */ 147304203a83SThomas Cort 147404203a83SThomas Cort /* check for trailing ';type=[aid]' */ 147504203a83SThomas Cort if (! EMPTYSTRING(path) && (cp = strrchr(path, ';')) != NULL) { 147604203a83SThomas Cort if (strcasecmp(cp, ";type=a") == 0) 147704203a83SThomas Cort transtype = TYPE_A; 147804203a83SThomas Cort else if (strcasecmp(cp, ";type=i") == 0) 147904203a83SThomas Cort transtype = TYPE_I; 148004203a83SThomas Cort else if (strcasecmp(cp, ";type=d") == 0) { 148104203a83SThomas Cort warnx( 148204203a83SThomas Cort "Directory listing via a URL is not supported"); 148304203a83SThomas Cort goto cleanup_fetch_ftp; 148404203a83SThomas Cort } else { 148504203a83SThomas Cort warnx("Invalid suffix `%s' in URL `%s'", cp, 148604203a83SThomas Cort url); 148704203a83SThomas Cort goto cleanup_fetch_ftp; 148804203a83SThomas Cort } 148904203a83SThomas Cort *cp = 0; 149004203a83SThomas Cort } 149104203a83SThomas Cort } else { /* classic style `[user@]host:[file]' */ 149204203a83SThomas Cort urltype = CLASSIC_URL_T; 149304203a83SThomas Cort host = ftp_strdup(url); 149404203a83SThomas Cort cp = strchr(host, '@'); 149504203a83SThomas Cort if (cp != NULL) { 149604203a83SThomas Cort *cp = '\0'; 149704203a83SThomas Cort uuser = host; 149804203a83SThomas Cort anonftp = 0; /* disable anonftp */ 149904203a83SThomas Cort host = ftp_strdup(cp + 1); 150004203a83SThomas Cort } 150104203a83SThomas Cort cp = strchr(host, ':'); 150204203a83SThomas Cort if (cp != NULL) { 150304203a83SThomas Cort *cp = '\0'; 150404203a83SThomas Cort path = ftp_strdup(cp + 1); 150504203a83SThomas Cort } 150604203a83SThomas Cort } 150704203a83SThomas Cort if (EMPTYSTRING(host)) 150804203a83SThomas Cort goto cleanup_fetch_ftp; 150904203a83SThomas Cort 151004203a83SThomas Cort /* Extract the file and (if present) directory name. */ 151104203a83SThomas Cort dir = path; 151204203a83SThomas Cort if (! EMPTYSTRING(dir)) { 151304203a83SThomas Cort /* 151404203a83SThomas Cort * If we are dealing with classic `[user@]host:[path]' syntax, 151504203a83SThomas Cort * then a path of the form `/file' (resulting from input of the 151604203a83SThomas Cort * form `host:/file') means that we should do "CWD /" before 151704203a83SThomas Cort * retrieving the file. So we set dir="/" and file="file". 151804203a83SThomas Cort * 151904203a83SThomas Cort * But if we are dealing with URLs like `ftp://host/path' then 152004203a83SThomas Cort * a path of the form `/file' (resulting from a URL of the form 152104203a83SThomas Cort * `ftp://host//file') means that we should do `CWD ' (with an 152204203a83SThomas Cort * empty argument) before retrieving the file. So we set 152304203a83SThomas Cort * dir="" and file="file". 152404203a83SThomas Cort * 152504203a83SThomas Cort * If the path does not contain / at all, we set dir=NULL. 152604203a83SThomas Cort * (We get a path without any slashes if we are dealing with 152704203a83SThomas Cort * classic `[user@]host:[file]' or URL `ftp://host/file'.) 152804203a83SThomas Cort * 152904203a83SThomas Cort * In all other cases, we set dir to a string that does not 153004203a83SThomas Cort * include the final '/' that separates the dir part from the 153104203a83SThomas Cort * file part of the path. (This will be the empty string if 153204203a83SThomas Cort * and only if we are dealing with a path of the form `/file' 153304203a83SThomas Cort * resulting from an URL of the form `ftp://host//file'.) 153404203a83SThomas Cort */ 153504203a83SThomas Cort cp = strrchr(dir, '/'); 153604203a83SThomas Cort if (cp == dir && urltype == CLASSIC_URL_T) { 153704203a83SThomas Cort file = cp + 1; 153804203a83SThomas Cort (void)strlcpy(dirbuf, "/", sizeof(dirbuf)); 153904203a83SThomas Cort dir = dirbuf; 154004203a83SThomas Cort } else if (cp != NULL) { 154104203a83SThomas Cort *cp++ = '\0'; 154204203a83SThomas Cort file = cp; 154304203a83SThomas Cort } else { 154404203a83SThomas Cort file = dir; 154504203a83SThomas Cort dir = NULL; 154604203a83SThomas Cort } 154704203a83SThomas Cort } else 154804203a83SThomas Cort dir = NULL; 154904203a83SThomas Cort if (urltype == FTP_URL_T && file != NULL) { 155004203a83SThomas Cort url_decode(file); 155104203a83SThomas Cort /* but still don't url_decode(dir) */ 155204203a83SThomas Cort } 155304203a83SThomas Cort DPRINTF("fetch_ftp: user `%s' pass `%s' host %s port %s " 155404203a83SThomas Cort "path `%s' dir `%s' file `%s'\n", 155504203a83SThomas Cort STRorNULL(uuser), STRorNULL(pass), 155604203a83SThomas Cort STRorNULL(host), STRorNULL(port), 155704203a83SThomas Cort STRorNULL(path), STRorNULL(dir), STRorNULL(file)); 155804203a83SThomas Cort 155904203a83SThomas Cort dirhasglob = filehasglob = 0; 156004203a83SThomas Cort if (doglob && urltype == CLASSIC_URL_T) { 156104203a83SThomas Cort if (! EMPTYSTRING(dir) && strpbrk(dir, "*?[]{}") != NULL) 156204203a83SThomas Cort dirhasglob = 1; 156304203a83SThomas Cort if (! EMPTYSTRING(file) && strpbrk(file, "*?[]{}") != NULL) 156404203a83SThomas Cort filehasglob = 1; 156504203a83SThomas Cort } 156604203a83SThomas Cort 156704203a83SThomas Cort /* Set up the connection */ 156804203a83SThomas Cort oanonftp = anonftp; 156904203a83SThomas Cort if (connected) 157004203a83SThomas Cort disconnect(0, NULL); 157104203a83SThomas Cort anonftp = oanonftp; 157204203a83SThomas Cort (void)strlcpy(cmdbuf, getprogname(), sizeof(cmdbuf)); 157304203a83SThomas Cort xargv[0] = cmdbuf; 157404203a83SThomas Cort xargv[1] = host; 157504203a83SThomas Cort xargv[2] = NULL; 157604203a83SThomas Cort xargc = 2; 157704203a83SThomas Cort if (port) { 157804203a83SThomas Cort xargv[2] = port; 157904203a83SThomas Cort xargv[3] = NULL; 158004203a83SThomas Cort xargc = 3; 158104203a83SThomas Cort } 158204203a83SThomas Cort oautologin = autologin; 158304203a83SThomas Cort /* don't autologin in setpeer(), use ftp_login() below */ 158404203a83SThomas Cort autologin = 0; 158504203a83SThomas Cort setpeer(xargc, xargv); 158604203a83SThomas Cort autologin = oautologin; 158704203a83SThomas Cort if ((connected == 0) || 158804203a83SThomas Cort (connected == 1 && !ftp_login(host, uuser, pass))) { 158904203a83SThomas Cort warnx("Can't connect or login to host `%s:%s'", 159004203a83SThomas Cort host, port ? port : "?"); 159104203a83SThomas Cort goto cleanup_fetch_ftp; 159204203a83SThomas Cort } 159304203a83SThomas Cort 159404203a83SThomas Cort switch (transtype) { 159504203a83SThomas Cort case TYPE_A: 159604203a83SThomas Cort setascii(1, xargv); 159704203a83SThomas Cort break; 159804203a83SThomas Cort case TYPE_I: 159904203a83SThomas Cort setbinary(1, xargv); 160004203a83SThomas Cort break; 160104203a83SThomas Cort default: 160204203a83SThomas Cort errx(1, "fetch_ftp: unknown transfer type %d", transtype); 160304203a83SThomas Cort } 160404203a83SThomas Cort 160504203a83SThomas Cort /* 160604203a83SThomas Cort * Change directories, if necessary. 160704203a83SThomas Cort * 160804203a83SThomas Cort * Note: don't use EMPTYSTRING(dir) below, because 160904203a83SThomas Cort * dir=="" means something different from dir==NULL. 161004203a83SThomas Cort */ 161104203a83SThomas Cort if (dir != NULL && !dirhasglob) { 161204203a83SThomas Cort char *nextpart; 161304203a83SThomas Cort 161404203a83SThomas Cort /* 161504203a83SThomas Cort * If we are dealing with a classic `[user@]host:[path]' 161604203a83SThomas Cort * (urltype is CLASSIC_URL_T) then we have a raw directory 161704203a83SThomas Cort * name (not encoded in any way) and we can change 161804203a83SThomas Cort * directories in one step. 161904203a83SThomas Cort * 162004203a83SThomas Cort * If we are dealing with an `ftp://host/path' URL 162104203a83SThomas Cort * (urltype is FTP_URL_T), then RFC 3986 says we need to 162204203a83SThomas Cort * send a separate CWD command for each unescaped "/" 162304203a83SThomas Cort * in the path, and we have to interpret %hex escaping 162404203a83SThomas Cort * *after* we find the slashes. It's possible to get 162504203a83SThomas Cort * empty components here, (from multiple adjacent 162604203a83SThomas Cort * slashes in the path) and RFC 3986 says that we should 162704203a83SThomas Cort * still do `CWD ' (with a null argument) in such cases. 162804203a83SThomas Cort * 162904203a83SThomas Cort * Many ftp servers don't support `CWD ', so if there's an 163004203a83SThomas Cort * error performing that command, bail out with a descriptive 163104203a83SThomas Cort * message. 163204203a83SThomas Cort * 163304203a83SThomas Cort * Examples: 163404203a83SThomas Cort * 163504203a83SThomas Cort * host: dir="", urltype=CLASSIC_URL_T 163604203a83SThomas Cort * logged in (to default directory) 163704203a83SThomas Cort * host:file dir=NULL, urltype=CLASSIC_URL_T 163804203a83SThomas Cort * "RETR file" 163904203a83SThomas Cort * host:dir/ dir="dir", urltype=CLASSIC_URL_T 164004203a83SThomas Cort * "CWD dir", logged in 164104203a83SThomas Cort * ftp://host/ dir="", urltype=FTP_URL_T 164204203a83SThomas Cort * logged in (to default directory) 164304203a83SThomas Cort * ftp://host/dir/ dir="dir", urltype=FTP_URL_T 164404203a83SThomas Cort * "CWD dir", logged in 164504203a83SThomas Cort * ftp://host/file dir=NULL, urltype=FTP_URL_T 164604203a83SThomas Cort * "RETR file" 164704203a83SThomas Cort * ftp://host//file dir="", urltype=FTP_URL_T 164804203a83SThomas Cort * "CWD ", "RETR file" 164904203a83SThomas Cort * host:/file dir="/", urltype=CLASSIC_URL_T 165004203a83SThomas Cort * "CWD /", "RETR file" 165104203a83SThomas Cort * ftp://host///file dir="/", urltype=FTP_URL_T 165204203a83SThomas Cort * "CWD ", "CWD ", "RETR file" 165304203a83SThomas Cort * ftp://host/%2F/file dir="%2F", urltype=FTP_URL_T 165404203a83SThomas Cort * "CWD /", "RETR file" 165504203a83SThomas Cort * ftp://host/foo/file dir="foo", urltype=FTP_URL_T 165604203a83SThomas Cort * "CWD foo", "RETR file" 165704203a83SThomas Cort * ftp://host/foo/bar/file dir="foo/bar" 165804203a83SThomas Cort * "CWD foo", "CWD bar", "RETR file" 165904203a83SThomas Cort * ftp://host//foo/bar/file dir="/foo/bar" 166004203a83SThomas Cort * "CWD ", "CWD foo", "CWD bar", "RETR file" 166104203a83SThomas Cort * ftp://host/foo//bar/file dir="foo//bar" 166204203a83SThomas Cort * "CWD foo", "CWD ", "CWD bar", "RETR file" 166304203a83SThomas Cort * ftp://host/%2F/foo/bar/file dir="%2F/foo/bar" 166404203a83SThomas Cort * "CWD /", "CWD foo", "CWD bar", "RETR file" 166504203a83SThomas Cort * ftp://host/%2Ffoo/bar/file dir="%2Ffoo/bar" 166604203a83SThomas Cort * "CWD /foo", "CWD bar", "RETR file" 166704203a83SThomas Cort * ftp://host/%2Ffoo%2Fbar/file dir="%2Ffoo%2Fbar" 166804203a83SThomas Cort * "CWD /foo/bar", "RETR file" 166904203a83SThomas Cort * ftp://host/%2Ffoo%2Fbar%2Ffile dir=NULL 167004203a83SThomas Cort * "RETR /foo/bar/file" 167104203a83SThomas Cort * 167204203a83SThomas Cort * Note that we don't need `dir' after this point. 167304203a83SThomas Cort */ 167404203a83SThomas Cort do { 167504203a83SThomas Cort if (urltype == FTP_URL_T) { 167604203a83SThomas Cort nextpart = strchr(dir, '/'); 167704203a83SThomas Cort if (nextpart) { 167804203a83SThomas Cort *nextpart = '\0'; 167904203a83SThomas Cort nextpart++; 168004203a83SThomas Cort } 168104203a83SThomas Cort url_decode(dir); 168204203a83SThomas Cort } else 168304203a83SThomas Cort nextpart = NULL; 168404203a83SThomas Cort DPRINTF("fetch_ftp: dir `%s', nextpart `%s'\n", 168504203a83SThomas Cort STRorNULL(dir), STRorNULL(nextpart)); 168604203a83SThomas Cort if (urltype == FTP_URL_T || *dir != '\0') { 168704203a83SThomas Cort (void)strlcpy(cmdbuf, "cd", sizeof(cmdbuf)); 168804203a83SThomas Cort xargv[0] = cmdbuf; 168904203a83SThomas Cort xargv[1] = dir; 169004203a83SThomas Cort xargv[2] = NULL; 169104203a83SThomas Cort dirchange = 0; 169204203a83SThomas Cort cd(2, xargv); 169304203a83SThomas Cort if (! dirchange) { 169404203a83SThomas Cort if (*dir == '\0' && code == 500) 169504203a83SThomas Cort fprintf(stderr, 169604203a83SThomas Cort "\n" 169704203a83SThomas Cort "ftp: The `CWD ' command (without a directory), which is required by\n" 169804203a83SThomas Cort " RFC 3986 to support the empty directory in the URL pathname (`//'),\n" 169904203a83SThomas Cort " conflicts with the server's conformance to RFC 959.\n" 170004203a83SThomas Cort " Try the same URL without the `//' in the URL pathname.\n" 170104203a83SThomas Cort "\n"); 170204203a83SThomas Cort goto cleanup_fetch_ftp; 170304203a83SThomas Cort } 170404203a83SThomas Cort } 170504203a83SThomas Cort dir = nextpart; 170604203a83SThomas Cort } while (dir != NULL); 170704203a83SThomas Cort } 170804203a83SThomas Cort 170904203a83SThomas Cort if (EMPTYSTRING(file)) { 171004203a83SThomas Cort rval = -1; 171104203a83SThomas Cort goto cleanup_fetch_ftp; 171204203a83SThomas Cort } 171304203a83SThomas Cort 171404203a83SThomas Cort if (dirhasglob) { 171504203a83SThomas Cort (void)strlcpy(rempath, dir, sizeof(rempath)); 171604203a83SThomas Cort (void)strlcat(rempath, "/", sizeof(rempath)); 171704203a83SThomas Cort (void)strlcat(rempath, file, sizeof(rempath)); 171804203a83SThomas Cort file = rempath; 171904203a83SThomas Cort } 172004203a83SThomas Cort 172104203a83SThomas Cort /* Fetch the file(s). */ 172204203a83SThomas Cort xargc = 2; 172304203a83SThomas Cort (void)strlcpy(cmdbuf, "get", sizeof(cmdbuf)); 172404203a83SThomas Cort xargv[0] = cmdbuf; 172504203a83SThomas Cort xargv[1] = file; 172604203a83SThomas Cort xargv[2] = NULL; 172704203a83SThomas Cort if (dirhasglob || filehasglob) { 172804203a83SThomas Cort int ointeractive; 172904203a83SThomas Cort 173004203a83SThomas Cort ointeractive = interactive; 173104203a83SThomas Cort interactive = 0; 173204203a83SThomas Cort if (restartautofetch) 173304203a83SThomas Cort (void)strlcpy(cmdbuf, "mreget", sizeof(cmdbuf)); 173404203a83SThomas Cort else 173504203a83SThomas Cort (void)strlcpy(cmdbuf, "mget", sizeof(cmdbuf)); 173604203a83SThomas Cort xargv[0] = cmdbuf; 173704203a83SThomas Cort mget(xargc, xargv); 173804203a83SThomas Cort interactive = ointeractive; 173904203a83SThomas Cort } else { 174004203a83SThomas Cort if (outfile == NULL) { 174104203a83SThomas Cort cp = strrchr(file, '/'); /* find savefile */ 174204203a83SThomas Cort if (cp != NULL) 174304203a83SThomas Cort outfile = cp + 1; 174404203a83SThomas Cort else 174504203a83SThomas Cort outfile = file; 174604203a83SThomas Cort } 174704203a83SThomas Cort xargv[2] = (char *)outfile; 174804203a83SThomas Cort xargv[3] = NULL; 174904203a83SThomas Cort xargc++; 175004203a83SThomas Cort if (restartautofetch) 175104203a83SThomas Cort reget(xargc, xargv); 175204203a83SThomas Cort else 175304203a83SThomas Cort get(xargc, xargv); 175404203a83SThomas Cort } 175504203a83SThomas Cort 175604203a83SThomas Cort if ((code / 100) == COMPLETE) 175704203a83SThomas Cort rval = 0; 175804203a83SThomas Cort 175904203a83SThomas Cort cleanup_fetch_ftp: 176004203a83SThomas Cort FREEPTR(port); 176104203a83SThomas Cort FREEPTR(host); 176204203a83SThomas Cort FREEPTR(path); 176304203a83SThomas Cort FREEPTR(uuser); 176404203a83SThomas Cort if (pass) 176504203a83SThomas Cort memset(pass, 0, strlen(pass)); 176604203a83SThomas Cort FREEPTR(pass); 176704203a83SThomas Cort return (rval); 176804203a83SThomas Cort } 176904203a83SThomas Cort 177004203a83SThomas Cort /* 177104203a83SThomas Cort * Retrieve the given file to outfile. 177204203a83SThomas Cort * Supports arguments of the form: 177304203a83SThomas Cort * "host:path", "ftp://host/path" if $ftpproxy, call fetch_url() else 177404203a83SThomas Cort * call fetch_ftp() 177504203a83SThomas Cort * "http://host/path" call fetch_url() to use HTTP 177604203a83SThomas Cort * "file:///path" call fetch_url() to copy 177704203a83SThomas Cort * "about:..." print a message 177804203a83SThomas Cort * 177904203a83SThomas Cort * Returns 1 on failure, 0 on completed xfer, -1 if ftp connection 178004203a83SThomas Cort * is still open (e.g, ftp xfer with trailing /) 178104203a83SThomas Cort */ 178204203a83SThomas Cort static int 178304203a83SThomas Cort go_fetch(const char *url) 178404203a83SThomas Cort { 178504203a83SThomas Cort char *proxyenv; 178604203a83SThomas Cort char *p; 178704203a83SThomas Cort 178804203a83SThomas Cort #ifndef NO_ABOUT 178904203a83SThomas Cort /* 179004203a83SThomas Cort * Check for about:* 179104203a83SThomas Cort */ 179204203a83SThomas Cort if (STRNEQUAL(url, ABOUT_URL)) { 179304203a83SThomas Cort url += sizeof(ABOUT_URL) -1; 179404203a83SThomas Cort if (strcasecmp(url, "ftp") == 0 || 179504203a83SThomas Cort strcasecmp(url, "tnftp") == 0) { 179604203a83SThomas Cort fputs( 179704203a83SThomas Cort "This version of ftp has been enhanced by Luke Mewburn <lukem@NetBSD.org>\n" 179804203a83SThomas Cort "for the NetBSD project. Execute `man ftp' for more details.\n", ttyout); 179904203a83SThomas Cort } else if (strcasecmp(url, "lukem") == 0) { 180004203a83SThomas Cort fputs( 180104203a83SThomas Cort "Luke Mewburn is the author of most of the enhancements in this ftp client.\n" 180204203a83SThomas Cort "Please email feedback to <lukem@NetBSD.org>.\n", ttyout); 180304203a83SThomas Cort } else if (strcasecmp(url, "netbsd") == 0) { 180404203a83SThomas Cort fputs( 180504203a83SThomas Cort "NetBSD is a freely available and redistributable UNIX-like operating system.\n" 180604203a83SThomas Cort "For more information, see http://www.NetBSD.org/\n", ttyout); 180704203a83SThomas Cort } else if (strcasecmp(url, "version") == 0) { 180804203a83SThomas Cort fprintf(ttyout, "Version: %s %s%s\n", 180904203a83SThomas Cort FTP_PRODUCT, FTP_VERSION, 181004203a83SThomas Cort #ifdef INET6 181104203a83SThomas Cort "" 181204203a83SThomas Cort #else 181304203a83SThomas Cort " (-IPv6)" 181404203a83SThomas Cort #endif 181504203a83SThomas Cort ); 181604203a83SThomas Cort } else { 181704203a83SThomas Cort fprintf(ttyout, "`%s' is an interesting topic.\n", url); 181804203a83SThomas Cort } 181904203a83SThomas Cort fputs("\n", ttyout); 182004203a83SThomas Cort return (0); 182104203a83SThomas Cort } 182204203a83SThomas Cort #endif 182304203a83SThomas Cort 182404203a83SThomas Cort /* 182504203a83SThomas Cort * Check for file:// and http:// URLs. 182604203a83SThomas Cort */ 182704203a83SThomas Cort if (STRNEQUAL(url, HTTP_URL) 182804203a83SThomas Cort #ifdef WITH_SSL 182904203a83SThomas Cort || STRNEQUAL(url, HTTPS_URL) 183004203a83SThomas Cort #endif 183104203a83SThomas Cort || STRNEQUAL(url, FILE_URL)) 183204203a83SThomas Cort return (fetch_url(url, NULL, NULL, NULL)); 183304203a83SThomas Cort 183404203a83SThomas Cort /* 183504203a83SThomas Cort * If it contains "://" but does not begin with ftp:// 183604203a83SThomas Cort * or something that was already handled, then it's 183704203a83SThomas Cort * unsupported. 183804203a83SThomas Cort * 183904203a83SThomas Cort * If it contains ":" but not "://" then we assume the 184004203a83SThomas Cort * part before the colon is a host name, not an URL scheme, 184104203a83SThomas Cort * so we don't try to match that here. 184204203a83SThomas Cort */ 184304203a83SThomas Cort if ((p = strstr(url, "://")) != NULL && ! STRNEQUAL(url, FTP_URL)) 184404203a83SThomas Cort errx(1, "Unsupported URL scheme `%.*s'", (int)(p - url), url); 184504203a83SThomas Cort 184604203a83SThomas Cort /* 184704203a83SThomas Cort * Try FTP URL-style and host:file arguments next. 184804203a83SThomas Cort * If ftpproxy is set with an FTP URL, use fetch_url() 184904203a83SThomas Cort * Othewise, use fetch_ftp(). 185004203a83SThomas Cort */ 185104203a83SThomas Cort proxyenv = getoptionvalue("ftp_proxy"); 185204203a83SThomas Cort if (!EMPTYSTRING(proxyenv) && STRNEQUAL(url, FTP_URL)) 185304203a83SThomas Cort return (fetch_url(url, NULL, NULL, NULL)); 185404203a83SThomas Cort 185504203a83SThomas Cort return (fetch_ftp(url)); 185604203a83SThomas Cort } 185704203a83SThomas Cort 185804203a83SThomas Cort /* 185904203a83SThomas Cort * Retrieve multiple files from the command line, 186004203a83SThomas Cort * calling go_fetch() for each file. 186104203a83SThomas Cort * 186204203a83SThomas Cort * If an ftp path has a trailing "/", the path will be cd-ed into and 186304203a83SThomas Cort * the connection remains open, and the function will return -1 186404203a83SThomas Cort * (to indicate the connection is alive). 186504203a83SThomas Cort * If an error occurs the return value will be the offset+1 in 186604203a83SThomas Cort * argv[] of the file that caused a problem (i.e, argv[x] 186704203a83SThomas Cort * returns x+1) 186804203a83SThomas Cort * Otherwise, 0 is returned if all files retrieved successfully. 186904203a83SThomas Cort */ 187004203a83SThomas Cort int 187104203a83SThomas Cort auto_fetch(int argc, char *argv[]) 187204203a83SThomas Cort { 187304203a83SThomas Cort volatile int argpos, rval; 187404203a83SThomas Cort 187504203a83SThomas Cort argpos = rval = 0; 187604203a83SThomas Cort 187704203a83SThomas Cort if (sigsetjmp(toplevel, 1)) { 187804203a83SThomas Cort if (connected) 187904203a83SThomas Cort disconnect(0, NULL); 188004203a83SThomas Cort if (rval > 0) 188104203a83SThomas Cort rval = argpos + 1; 188204203a83SThomas Cort return (rval); 188304203a83SThomas Cort } 188404203a83SThomas Cort (void)xsignal(SIGINT, intr); 188504203a83SThomas Cort (void)xsignal(SIGPIPE, lostpeer); 188604203a83SThomas Cort 188704203a83SThomas Cort /* 188804203a83SThomas Cort * Loop through as long as there's files to fetch. 188904203a83SThomas Cort */ 189004203a83SThomas Cort for (; (rval == 0) && (argpos < argc); argpos++) { 189104203a83SThomas Cort if (strchr(argv[argpos], ':') == NULL) 189204203a83SThomas Cort break; 189304203a83SThomas Cort redirect_loop = 0; 189404203a83SThomas Cort if (!anonftp) 189504203a83SThomas Cort anonftp = 2; /* Handle "automatic" transfers. */ 189604203a83SThomas Cort rval = go_fetch(argv[argpos]); 189704203a83SThomas Cort if (outfile != NULL && strcmp(outfile, "-") != 0 189804203a83SThomas Cort && outfile[0] != '|') 189904203a83SThomas Cort outfile = NULL; 190004203a83SThomas Cort if (rval > 0) 190104203a83SThomas Cort rval = argpos + 1; 190204203a83SThomas Cort } 190304203a83SThomas Cort 190404203a83SThomas Cort if (connected && rval != -1) 190504203a83SThomas Cort disconnect(0, NULL); 190604203a83SThomas Cort return (rval); 190704203a83SThomas Cort } 190804203a83SThomas Cort 190904203a83SThomas Cort 191004203a83SThomas Cort /* 191104203a83SThomas Cort * Upload multiple files from the command line. 191204203a83SThomas Cort * 191304203a83SThomas Cort * If an error occurs the return value will be the offset+1 in 191404203a83SThomas Cort * argv[] of the file that caused a problem (i.e, argv[x] 191504203a83SThomas Cort * returns x+1) 191604203a83SThomas Cort * Otherwise, 0 is returned if all files uploaded successfully. 191704203a83SThomas Cort */ 191804203a83SThomas Cort int 191904203a83SThomas Cort auto_put(int argc, char **argv, const char *uploadserver) 192004203a83SThomas Cort { 192104203a83SThomas Cort char *uargv[4], *path, *pathsep; 192204203a83SThomas Cort int uargc, rval, argpos; 192304203a83SThomas Cort size_t len; 192404203a83SThomas Cort char cmdbuf[MAX_C_NAME]; 192504203a83SThomas Cort 192604203a83SThomas Cort (void)strlcpy(cmdbuf, "mput", sizeof(cmdbuf)); 192704203a83SThomas Cort uargv[0] = cmdbuf; 192804203a83SThomas Cort uargv[1] = argv[0]; 192904203a83SThomas Cort uargc = 2; 193004203a83SThomas Cort uargv[2] = uargv[3] = NULL; 193104203a83SThomas Cort pathsep = NULL; 193204203a83SThomas Cort rval = 1; 193304203a83SThomas Cort 193404203a83SThomas Cort DPRINTF("auto_put: target `%s'\n", uploadserver); 193504203a83SThomas Cort 193604203a83SThomas Cort path = ftp_strdup(uploadserver); 193704203a83SThomas Cort len = strlen(path); 193804203a83SThomas Cort if (path[len - 1] != '/' && path[len - 1] != ':') { 193904203a83SThomas Cort /* 194004203a83SThomas Cort * make sure we always pass a directory to auto_fetch 194104203a83SThomas Cort */ 194204203a83SThomas Cort if (argc > 1) { /* more than one file to upload */ 194304203a83SThomas Cort len = strlen(uploadserver) + 2; /* path + "/" + "\0" */ 194404203a83SThomas Cort free(path); 194504203a83SThomas Cort path = (char *)ftp_malloc(len); 194604203a83SThomas Cort (void)strlcpy(path, uploadserver, len); 194704203a83SThomas Cort (void)strlcat(path, "/", len); 194804203a83SThomas Cort } else { /* single file to upload */ 194904203a83SThomas Cort (void)strlcpy(cmdbuf, "put", sizeof(cmdbuf)); 195004203a83SThomas Cort uargv[0] = cmdbuf; 195104203a83SThomas Cort pathsep = strrchr(path, '/'); 195204203a83SThomas Cort if (pathsep == NULL) { 195304203a83SThomas Cort pathsep = strrchr(path, ':'); 195404203a83SThomas Cort if (pathsep == NULL) { 195504203a83SThomas Cort warnx("Invalid URL `%s'", path); 195604203a83SThomas Cort goto cleanup_auto_put; 195704203a83SThomas Cort } 195804203a83SThomas Cort pathsep++; 195904203a83SThomas Cort uargv[2] = ftp_strdup(pathsep); 196004203a83SThomas Cort pathsep[0] = '/'; 196104203a83SThomas Cort } else 196204203a83SThomas Cort uargv[2] = ftp_strdup(pathsep + 1); 196304203a83SThomas Cort pathsep[1] = '\0'; 196404203a83SThomas Cort uargc++; 196504203a83SThomas Cort } 196604203a83SThomas Cort } 196704203a83SThomas Cort DPRINTF("auto_put: URL `%s' argv[2] `%s'\n", 196804203a83SThomas Cort path, STRorNULL(uargv[2])); 196904203a83SThomas Cort 197004203a83SThomas Cort /* connect and cwd */ 197104203a83SThomas Cort rval = auto_fetch(1, &path); 197204203a83SThomas Cort if(rval >= 0) 197304203a83SThomas Cort goto cleanup_auto_put; 197404203a83SThomas Cort 197504203a83SThomas Cort rval = 0; 197604203a83SThomas Cort 197704203a83SThomas Cort /* target filename provided; upload 1 file */ 197804203a83SThomas Cort /* XXX : is this the best way? */ 197904203a83SThomas Cort if (uargc == 3) { 198004203a83SThomas Cort uargv[1] = argv[0]; 198104203a83SThomas Cort put(uargc, uargv); 198204203a83SThomas Cort if ((code / 100) != COMPLETE) 198304203a83SThomas Cort rval = 1; 198404203a83SThomas Cort } else { /* otherwise a target dir: upload all files to it */ 198504203a83SThomas Cort for(argpos = 0; argv[argpos] != NULL; argpos++) { 198604203a83SThomas Cort uargv[1] = argv[argpos]; 198704203a83SThomas Cort mput(uargc, uargv); 198804203a83SThomas Cort if ((code / 100) != COMPLETE) { 198904203a83SThomas Cort rval = argpos + 1; 199004203a83SThomas Cort break; 199104203a83SThomas Cort } 199204203a83SThomas Cort } 199304203a83SThomas Cort } 199404203a83SThomas Cort 199504203a83SThomas Cort cleanup_auto_put: 199604203a83SThomas Cort free(path); 199704203a83SThomas Cort FREEPTR(uargv[2]); 199804203a83SThomas Cort return (rval); 199904203a83SThomas Cort } 2000