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