fetch.c revision 1.182
11.182Slukem/* $NetBSD: fetch.c,v 1.182 2007/08/22 23:47:13 lukem Exp $ */ 21.1Slukem 31.1Slukem/*- 41.174Slukem * Copyright (c) 1997-2007 The NetBSD Foundation, Inc. 51.1Slukem * All rights reserved. 61.1Slukem * 71.1Slukem * This code is derived from software contributed to The NetBSD Foundation 81.54Slukem * by Luke Mewburn. 91.1Slukem * 101.114Slukem * This code is derived from software contributed to The NetBSD Foundation 111.114Slukem * by Scott Aaron Bamford. 121.114Slukem * 131.1Slukem * Redistribution and use in source and binary forms, with or without 141.1Slukem * modification, are permitted provided that the following conditions 151.1Slukem * are met: 161.1Slukem * 1. Redistributions of source code must retain the above copyright 171.1Slukem * notice, this list of conditions and the following disclaimer. 181.1Slukem * 2. Redistributions in binary form must reproduce the above copyright 191.1Slukem * notice, this list of conditions and the following disclaimer in the 201.1Slukem * documentation and/or other materials provided with the distribution. 211.1Slukem * 3. All advertising materials mentioning features or use of this software 221.1Slukem * must display the following acknowledgement: 231.84Slukem * This product includes software developed by the NetBSD 241.84Slukem * Foundation, Inc. and its contributors. 251.1Slukem * 4. Neither the name of The NetBSD Foundation nor the names of its 261.1Slukem * contributors may be used to endorse or promote products derived 271.1Slukem * from this software without specific prior written permission. 281.1Slukem * 291.1Slukem * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS 301.1Slukem * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED 311.1Slukem * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 321.14Slukem * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS 331.14Slukem * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 341.1Slukem * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 351.1Slukem * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 361.1Slukem * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 371.1Slukem * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 381.1Slukem * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 391.1Slukem * POSSIBILITY OF SUCH DAMAGE. 401.1Slukem */ 411.1Slukem 421.12Slukem#include <sys/cdefs.h> 431.1Slukem#ifndef lint 441.182Slukem__RCSID("$NetBSD: fetch.c,v 1.182 2007/08/22 23:47:13 lukem Exp $"); 451.1Slukem#endif /* not lint */ 461.1Slukem 471.1Slukem/* 481.1Slukem * FTP User Program -- Command line file retrieval 491.1Slukem */ 501.1Slukem 511.1Slukem#include <sys/types.h> 521.1Slukem#include <sys/param.h> 531.1Slukem#include <sys/socket.h> 541.30Slukem#include <sys/stat.h> 551.32Slukem#include <sys/time.h> 561.1Slukem 571.1Slukem#include <netinet/in.h> 581.1Slukem 591.2Slukem#include <arpa/ftp.h> 601.1Slukem#include <arpa/inet.h> 611.1Slukem 621.1Slukem#include <ctype.h> 631.1Slukem#include <err.h> 641.22Slukem#include <errno.h> 651.1Slukem#include <netdb.h> 661.1Slukem#include <fcntl.h> 671.1Slukem#include <stdio.h> 681.1Slukem#include <stdlib.h> 691.1Slukem#include <string.h> 701.1Slukem#include <unistd.h> 711.57Schristos#include <time.h> 721.1Slukem 731.1Slukem#include "ftp_var.h" 741.100Slukem#include "version.h" 751.1Slukem 761.27Slukemtypedef enum { 771.27Slukem UNKNOWN_URL_T=-1, 781.27Slukem HTTP_URL_T, 791.27Slukem FTP_URL_T, 801.53Slukem FILE_URL_T, 811.53Slukem CLASSIC_URL_T 821.27Slukem} url_t; 831.27Slukem 841.111Slukemvoid aborthttp(int); 851.147Schristos#ifndef NO_AUTH 861.111Slukemstatic int auth_url(const char *, char **, const char *, const char *); 871.154Slukemstatic void base64_encode(const unsigned char *, size_t, unsigned char *); 881.147Schristos#endif 891.111Slukemstatic int go_fetch(const char *); 901.111Slukemstatic int fetch_ftp(const char *); 911.111Slukemstatic int fetch_url(const char *, const char *, char *, char *); 921.154Slukemstatic const char *match_token(const char **, const char *); 931.111Slukemstatic int parse_url(const char *, const char *, url_t *, char **, 941.111Slukem char **, char **, char **, in_port_t *, char **); 951.111Slukemstatic void url_decode(char *); 961.12Slukem 971.42Slukemstatic int redirect_loop; 981.42Slukem 991.12Slukem 1001.150Slukem#define STRNEQUAL(a,b) (strncasecmp((a), (b), sizeof((b))-1) == 0) 1011.150Slukem#define ISLWS(x) ((x)=='\r' || (x)=='\n' || (x)==' ' || (x)=='\t') 1021.150Slukem#define SKIPLWS(x) do { while (ISLWS((*x))) x++; } while (0) 1031.150Slukem 1041.150Slukem 1051.25Slukem#define ABOUT_URL "about:" /* propaganda */ 1061.25Slukem#define FILE_URL "file://" /* file URL prefix */ 1071.1Slukem#define FTP_URL "ftp://" /* ftp URL prefix */ 1081.1Slukem#define HTTP_URL "http://" /* http URL prefix */ 1091.1Slukem 1101.1Slukem 1111.154Slukem/* 1121.154Slukem * Determine if token is the next word in buf (case insensitive). 1131.154Slukem * If so, advance buf past the token and any trailing LWS, and 1141.154Slukem * return a pointer to the token (in buf). Otherwise, return NULL. 1151.172Schristos * token may be preceded by LWS. 1161.154Slukem * token must be followed by LWS or NUL. (I.e, don't partial match). 1171.154Slukem */ 1181.154Slukemstatic const char * 1191.154Slukemmatch_token(const char **buf, const char *token) 1201.154Slukem{ 1211.154Slukem const char *p, *orig; 1221.154Slukem size_t tlen; 1231.154Slukem 1241.154Slukem tlen = strlen(token); 1251.154Slukem p = *buf; 1261.154Slukem SKIPLWS(p); 1271.154Slukem orig = p; 1281.154Slukem if (strncasecmp(p, token, tlen) != 0) 1291.154Slukem return NULL; 1301.154Slukem p += tlen; 1311.154Slukem if (*p != '\0' && !ISLWS(*p)) 1321.154Slukem return NULL; 1331.154Slukem SKIPLWS(p); 1341.154Slukem orig = *buf; 1351.154Slukem *buf = p; 1361.154Slukem return orig; 1371.154Slukem} 1381.154Slukem 1391.147Schristos#ifndef NO_AUTH 1401.27Slukem/* 1411.44Slukem * Generate authorization response based on given authentication challenge. 1421.44Slukem * Returns -1 if an error occurred, otherwise 0. 1431.44Slukem * Sets response to a malloc(3)ed string; caller should free. 1441.44Slukem */ 1451.44Slukemstatic int 1461.111Slukemauth_url(const char *challenge, char **response, const char *guser, 1471.111Slukem const char *gpass) 1481.44Slukem{ 1491.162Slukem const char *cp, *scheme, *errormsg; 1501.154Slukem char *ep, *clear, *realm; 1511.82Slukem char user[BUFSIZ], *pass; 1521.82Slukem int rval; 1531.82Slukem size_t len, clen, rlen; 1541.44Slukem 1551.44Slukem *response = NULL; 1561.154Slukem clear = realm = NULL; 1571.44Slukem rval = -1; 1581.154Slukem cp = challenge; 1591.154Slukem scheme = "Basic"; /* only support Basic authentication */ 1601.44Slukem 1611.163Schristos DPRINTF("auth_url: challenge `%s'\n", challenge); 1621.44Slukem 1631.154Slukem if (! match_token(&cp, scheme)) { 1641.175Slukem warnx("Unsupported authentication challenge `%s'", 1651.44Slukem challenge); 1661.44Slukem goto cleanup_auth_url; 1671.44Slukem } 1681.44Slukem 1691.91Slukem#define REALM "realm=\"" 1701.150Slukem if (STRNEQUAL(cp, REALM)) 1711.44Slukem cp += sizeof(REALM) - 1; 1721.44Slukem else { 1731.175Slukem warnx("Unsupported authentication challenge `%s'", 1741.44Slukem challenge); 1751.44Slukem goto cleanup_auth_url; 1761.44Slukem } 1771.154Slukem/* XXX: need to improve quoted-string parsing to support \ quoting, etc. */ 1781.44Slukem if ((ep = strchr(cp, '\"')) != NULL) { 1791.44Slukem size_t len = ep - cp; 1801.44Slukem 1811.166Schristos realm = (char *)ftp_malloc(len + 1); 1821.78Slukem (void)strlcpy(realm, cp, len + 1); 1831.44Slukem } else { 1841.175Slukem warnx("Unsupported authentication challenge `%s'", 1851.44Slukem challenge); 1861.44Slukem goto cleanup_auth_url; 1871.44Slukem } 1881.44Slukem 1891.154Slukem fprintf(ttyout, "Username for `%s': ", realm); 1901.154Slukem if (guser != NULL) { 1911.78Slukem (void)strlcpy(user, guser, sizeof(user)); 1921.154Slukem fprintf(ttyout, "%s\n", user); 1931.154Slukem } else { 1941.54Slukem (void)fflush(ttyout); 1951.162Slukem if (getline(stdin, user, sizeof(user), &errormsg) < 0) { 1961.162Slukem warnx("%s; can't authenticate", errormsg); 1971.54Slukem goto cleanup_auth_url; 1981.90Slukem } 1991.54Slukem } 2001.54Slukem if (gpass != NULL) 2011.54Slukem pass = (char *)gpass; 2021.174Slukem else { 2031.54Slukem pass = getpass("Password: "); 2041.174Slukem if (pass == NULL) { 2051.174Slukem warnx("Can't read password"); 2061.174Slukem goto cleanup_auth_url; 2071.174Slukem } 2081.174Slukem } 2091.44Slukem 2101.72Slukem clen = strlen(user) + strlen(pass) + 2; /* user + ":" + pass + "\0" */ 2111.166Schristos clear = (char *)ftp_malloc(clen); 2121.78Slukem (void)strlcpy(clear, user, clen); 2131.78Slukem (void)strlcat(clear, ":", clen); 2141.78Slukem (void)strlcat(clear, pass, clen); 2151.54Slukem if (gpass == NULL) 2161.80Slukem memset(pass, 0, strlen(pass)); 2171.44Slukem 2181.64Slukem /* scheme + " " + enc + "\0" */ 2191.74Slukem rlen = strlen(scheme) + 1 + (clen + 2) * 4 / 3 + 1; 2201.166Schristos *response = (char *)ftp_malloc(rlen); 2211.78Slukem (void)strlcpy(*response, scheme, rlen); 2221.72Slukem len = strlcat(*response, " ", rlen); 2231.152Slukem /* use `clen - 1' to not encode the trailing NUL */ 2241.158Slukem base64_encode((unsigned char *)clear, clen - 1, 2251.158Slukem (unsigned char *)*response + len); 2261.80Slukem memset(clear, 0, clen); 2271.44Slukem rval = 0; 2281.44Slukem 2291.118Slukem cleanup_auth_url: 2301.44Slukem FREEPTR(clear); 2311.44Slukem FREEPTR(realm); 2321.44Slukem return (rval); 2331.44Slukem} 2341.44Slukem 2351.44Slukem/* 2361.44Slukem * Encode len bytes starting at clear using base64 encoding into encoded, 2371.44Slukem * which should be at least ((len + 2) * 4 / 3 + 1) in size. 2381.44Slukem */ 2391.124Slukemstatic void 2401.154Slukembase64_encode(const unsigned char *clear, size_t len, unsigned char *encoded) 2411.44Slukem{ 2421.154Slukem static const unsigned char enc[] = 2431.44Slukem "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; 2441.154Slukem unsigned char *cp; 2451.82Slukem int i; 2461.44Slukem 2471.44Slukem cp = encoded; 2481.44Slukem for (i = 0; i < len; i += 3) { 2491.44Slukem *(cp++) = enc[((clear[i + 0] >> 2))]; 2501.44Slukem *(cp++) = enc[((clear[i + 0] << 4) & 0x30) 2511.44Slukem | ((clear[i + 1] >> 4) & 0x0f)]; 2521.44Slukem *(cp++) = enc[((clear[i + 1] << 2) & 0x3c) 2531.44Slukem | ((clear[i + 2] >> 6) & 0x03)]; 2541.44Slukem *(cp++) = enc[((clear[i + 2] ) & 0x3f)]; 2551.44Slukem } 2561.44Slukem *cp = '\0'; 2571.44Slukem while (i-- > len) 2581.44Slukem *(--cp) = '='; 2591.44Slukem} 2601.147Schristos#endif 2611.44Slukem 2621.50Slukem/* 2631.50Slukem * Decode %xx escapes in given string, `in-place'. 2641.50Slukem */ 2651.50Slukemstatic void 2661.111Slukemurl_decode(char *url) 2671.50Slukem{ 2681.50Slukem unsigned char *p, *q; 2691.50Slukem 2701.50Slukem if (EMPTYSTRING(url)) 2711.50Slukem return; 2721.75Slukem p = q = (unsigned char *)url; 2731.50Slukem 2741.91Slukem#define HEXTOINT(x) (x - (isdigit(x) ? '0' : (islower(x) ? 'a' : 'A') - 10)) 2751.50Slukem while (*p) { 2761.50Slukem if (p[0] == '%' 2771.50Slukem && p[1] && isxdigit((unsigned char)p[1]) 2781.50Slukem && p[2] && isxdigit((unsigned char)p[2])) { 2791.50Slukem *q++ = HEXTOINT(p[1]) * 16 + HEXTOINT(p[2]); 2801.50Slukem p+=3; 2811.50Slukem } else 2821.50Slukem *q++ = *p++; 2831.50Slukem } 2841.50Slukem *q = '\0'; 2851.50Slukem} 2861.50Slukem 2871.44Slukem 2881.44Slukem/* 2891.177Slukem * Parse URL of form (per RFC3986): 2901.145Slukem * <type>://[<user>[:<password>]@]<host>[:<port>][/<path>] 2911.27Slukem * Returns -1 if a parse error occurred, otherwise 0. 2921.50Slukem * It's the caller's responsibility to url_decode() the returned 2931.50Slukem * user, pass and path. 2941.63Slukem * 2951.27Slukem * Sets type to url_t, each of the given char ** pointers to a 2961.27Slukem * malloc(3)ed strings of the relevant section, and port to 2971.41Slukem * the number given, or ftpport if ftp://, or httpport if http://. 2981.52Slukem * 2991.177Slukem * XXX: this is not totally RFC3986 compliant; <path> will have the 3001.53Slukem * leading `/' unless it's an ftp:// URL, as this makes things easier 3011.177Slukem * for file:// and http:// URLs. ftp:// URLs have the `/' between the 3021.141Swiz * host and the URL-path removed, but any additional leading slashes 3031.141Swiz * in the URL-path are retained (because they imply that we should 3041.53Slukem * later do "CWD" with a null argument). 3051.53Slukem * 3061.53Slukem * Examples: 3071.141Swiz * input URL output path 3081.53Slukem * --------- ----------- 3091.177Slukem * "http://host" "/" 3101.177Slukem * "http://host/" "/" 3111.177Slukem * "http://host/path" "/path" 3121.63Slukem * "file://host/dir/file" "dir/file" 3131.177Slukem * "ftp://host" "" 3141.63Slukem * "ftp://host/" "" 3151.177Slukem * "ftp://host//" "/" 3161.177Slukem * "ftp://host/dir/file" "dir/file" 3171.53Slukem * "ftp://host//dir/file" "/dir/file" 3181.27Slukem */ 3191.27Slukemstatic int 3201.111Slukemparse_url(const char *url, const char *desc, url_t *type, 3211.111Slukem char **user, char **pass, char **host, char **port, 3221.111Slukem in_port_t *portnum, char **path) 3231.27Slukem{ 3241.82Slukem const char *origurl; 3251.82Slukem char *cp, *ep, *thost, *tport; 3261.82Slukem size_t len; 3271.27Slukem 3281.27Slukem if (url == NULL || desc == NULL || type == NULL || user == NULL 3291.82Slukem || pass == NULL || host == NULL || port == NULL || portnum == NULL 3301.82Slukem || path == NULL) 3311.27Slukem errx(1, "parse_url: invoked with NULL argument!"); 3321.27Slukem 3331.82Slukem origurl = url; 3341.27Slukem *type = UNKNOWN_URL_T; 3351.63Slukem *user = *pass = *host = *port = *path = NULL; 3361.82Slukem *portnum = 0; 3371.63Slukem tport = NULL; 3381.27Slukem 3391.150Slukem if (STRNEQUAL(url, HTTP_URL)) { 3401.27Slukem url += sizeof(HTTP_URL) - 1; 3411.27Slukem *type = HTTP_URL_T; 3421.82Slukem *portnum = HTTP_PORT; 3431.63Slukem tport = httpport; 3441.150Slukem } else if (STRNEQUAL(url, FTP_URL)) { 3451.27Slukem url += sizeof(FTP_URL) - 1; 3461.27Slukem *type = FTP_URL_T; 3471.82Slukem *portnum = FTP_PORT; 3481.63Slukem tport = ftpport; 3491.150Slukem } else if (STRNEQUAL(url, FILE_URL)) { 3501.27Slukem url += sizeof(FILE_URL) - 1; 3511.27Slukem *type = FILE_URL_T; 3521.27Slukem } else { 3531.27Slukem warnx("Invalid %s `%s'", desc, url); 3541.118Slukem cleanup_parse_url: 3551.27Slukem FREEPTR(*user); 3561.162Slukem if (*pass != NULL) 3571.162Slukem memset(*pass, 0, strlen(*pass)); 3581.27Slukem FREEPTR(*pass); 3591.27Slukem FREEPTR(*host); 3601.60Sitojun FREEPTR(*port); 3611.27Slukem FREEPTR(*path); 3621.27Slukem return (-1); 3631.27Slukem } 3641.27Slukem 3651.27Slukem if (*url == '\0') 3661.27Slukem return (0); 3671.27Slukem 3681.27Slukem /* find [user[:pass]@]host[:port] */ 3691.27Slukem ep = strchr(url, '/'); 3701.27Slukem if (ep == NULL) 3711.166Schristos thost = ftp_strdup(url); 3721.27Slukem else { 3731.50Slukem len = ep - url; 3741.166Schristos thost = (char *)ftp_malloc(len + 1); 3751.78Slukem (void)strlcpy(thost, url, len + 1); 3761.53Slukem if (*type == FTP_URL_T) /* skip first / for ftp URLs */ 3771.53Slukem ep++; 3781.166Schristos *path = ftp_strdup(ep); 3791.27Slukem } 3801.27Slukem 3811.63Slukem cp = strchr(thost, '@'); /* look for user[:pass]@ in URLs */ 3821.54Slukem if (cp != NULL) { 3831.54Slukem if (*type == FTP_URL_T) 3841.54Slukem anonftp = 0; /* disable anonftp */ 3851.27Slukem *user = thost; 3861.27Slukem *cp = '\0'; 3871.166Schristos thost = ftp_strdup(cp + 1); 3881.27Slukem cp = strchr(*user, ':'); 3891.27Slukem if (cp != NULL) { 3901.27Slukem *cp = '\0'; 3911.166Schristos *pass = ftp_strdup(cp + 1); 3921.27Slukem } 3931.154Slukem url_decode(*user); 3941.154Slukem if (*pass) 3951.154Slukem url_decode(*pass); 3961.63Slukem } 3971.63Slukem 3981.63Slukem#ifdef INET6 3991.63Slukem /* 4001.63Slukem * Check if thost is an encoded IPv6 address, as per 4011.177Slukem * RFC3986: 4021.63Slukem * `[' ipv6-address ']' 4031.63Slukem */ 4041.63Slukem if (*thost == '[') { 4051.63Slukem cp = thost + 1; 4061.63Slukem if ((ep = strchr(cp, ']')) == NULL || 4071.106Sitojun (ep[1] != '\0' && ep[1] != ':')) { 4081.63Slukem warnx("Invalid address `%s' in %s `%s'", 4091.82Slukem thost, desc, origurl); 4101.63Slukem goto cleanup_parse_url; 4111.63Slukem } 4121.103Slukem len = ep - cp; /* change `[xyz]' -> `xyz' */ 4131.63Slukem memmove(thost, thost + 1, len); 4141.63Slukem thost[len] = '\0'; 4151.63Slukem if (! isipv6addr(thost)) { 4161.63Slukem warnx("Invalid IPv6 address `%s' in %s `%s'", 4171.82Slukem thost, desc, origurl); 4181.63Slukem goto cleanup_parse_url; 4191.63Slukem } 4201.63Slukem cp = ep + 1; 4211.63Slukem if (*cp == ':') 4221.63Slukem cp++; 4231.63Slukem else 4241.63Slukem cp = NULL; 4251.27Slukem } else 4261.63Slukem#endif /* INET6 */ 4271.177Slukem if ((cp = strchr(thost, ':')) != NULL) 4281.177Slukem *cp++ = '\0'; 4291.63Slukem *host = thost; 4301.60Sitojun 4311.27Slukem /* look for [:port] */ 4321.27Slukem if (cp != NULL) { 4331.82Slukem long nport; 4341.27Slukem 4351.122Slukem nport = parseport(cp, -1); 4361.122Slukem if (nport == -1) { 4371.122Slukem warnx("Unknown port `%s' in %s `%s'", 4381.122Slukem cp, desc, origurl); 4391.27Slukem goto cleanup_parse_url; 4401.27Slukem } 4411.82Slukem *portnum = nport; 4421.63Slukem tport = cp; 4431.27Slukem } 4441.82Slukem 4451.95Slukem if (tport != NULL) 4461.166Schristos *port = ftp_strdup(tport); 4471.177Slukem if (*path == NULL) { 4481.177Slukem const char *emptypath = "/"; 4491.177Slukem if (*type == FTP_URL_T) /* skip first / for ftp URLs */ 4501.177Slukem emptypath++; 4511.177Slukem *path = ftp_strdup(emptypath); 4521.177Slukem } 4531.27Slukem 4541.163Schristos DPRINTF("parse_url: user `%s' pass `%s' host %s port %s(%d) " 4551.163Schristos "path `%s'\n", 4561.163Schristos *user ? *user : "<null>", *pass ? *pass : "<null>", 4571.163Schristos *host ? *host : "<null>", *port ? *port : "<null>", 4581.163Schristos *portnum ? *portnum : -1, *path ? *path : "<null>"); 4591.27Slukem 4601.27Slukem return (0); 4611.27Slukem} 4621.27Slukem 4631.89Slukemsigjmp_buf httpabort; 4641.2Slukem 4651.1Slukem/* 4661.50Slukem * Retrieve URL, via a proxy if necessary, using HTTP. 4671.50Slukem * If proxyenv is set, use that for the proxy, otherwise try ftp_proxy or 4681.50Slukem * http_proxy as appropriate. 4691.50Slukem * Supports HTTP redirects. 4701.127Stacha * Returns 1 on failure, 0 on completed xfer, -1 if ftp connection 4711.41Slukem * is still open (e.g, ftp xfer with trailing /) 4721.1Slukem */ 4731.12Slukemstatic int 4741.111Slukemfetch_url(const char *url, const char *proxyenv, char *proxyauth, char *wwwauth) 4751.1Slukem{ 4761.98Sitojun struct addrinfo hints, *res, *res0 = NULL; 4771.66Schristos int error; 4781.98Sitojun char hbuf[NI_MAXHOST]; 4791.173Schristos sigfunc volatile oldintr; 4801.173Schristos sigfunc volatile oldintp; 4811.173Schristos int volatile s; 4821.95Slukem struct stat sb; 4831.173Schristos int volatile ischunked; 4841.173Schristos int volatile isproxy; 4851.173Schristos int volatile rval; 4861.173Schristos int volatile hcode; 4871.42Slukem size_t len; 4881.73Slukem static size_t bufsize; 4891.73Slukem static char *xferbuf; 4901.154Slukem const char *cp, *token; 4911.173Schristos char *ep; 4921.176Slukem char buf[FTPBUFLEN]; 4931.176Slukem const char *errormsg; 4941.173Schristos char *volatile savefile; 4951.173Schristos char *volatile auth; 4961.173Schristos char *volatile location; 4971.173Schristos char *volatile message; 4981.173Schristos char *user, *pass, *host, *port, *path; 4991.173Schristos char *volatile decodedpath; 5001.144Slukem char *puser, *ppass, *useragent; 5011.95Slukem off_t hashbytes, rangestart, rangeend, entitylen; 5021.173Schristos int (*volatile closefunc)(FILE *); 5031.173Schristos FILE *volatile fin; 5041.173Schristos FILE *volatile fout; 5051.42Slukem time_t mtime; 5061.42Slukem url_t urltype; 5071.60Sitojun in_port_t portnum; 5081.1Slukem 5091.86Slukem oldintr = oldintp = NULL; 5101.22Slukem closefunc = NULL; 5111.24Slukem fin = fout = NULL; 5121.1Slukem s = -1; 5131.176Slukem savefile = NULL; 5141.44Slukem auth = location = message = NULL; 5151.52Slukem ischunked = isproxy = hcode = 0; 5161.42Slukem rval = 1; 5171.54Slukem user = pass = host = path = decodedpath = puser = ppass = NULL; 5181.1Slukem 5191.82Slukem if (parse_url(url, "URL", &urltype, &user, &pass, &host, &port, 5201.82Slukem &portnum, &path) == -1) 5211.42Slukem goto cleanup_fetch_url; 5221.5Slukem 5231.27Slukem if (urltype == FILE_URL_T && ! EMPTYSTRING(host) 5241.27Slukem && strcasecmp(host, "localhost") != 0) { 5251.27Slukem warnx("No support for non local file URL `%s'", url); 5261.42Slukem goto cleanup_fetch_url; 5271.9Slukem } 5281.27Slukem 5291.9Slukem if (EMPTYSTRING(path)) { 5301.42Slukem if (urltype == FTP_URL_T) { 5311.52Slukem rval = fetch_ftp(url); 5321.42Slukem goto cleanup_fetch_url; 5331.42Slukem } 5341.27Slukem if (urltype != HTTP_URL_T || outfile == NULL) { 5351.27Slukem warnx("Invalid URL (no file after host) `%s'", url); 5361.42Slukem goto cleanup_fetch_url; 5371.27Slukem } 5381.9Slukem } 5391.1Slukem 5401.166Schristos decodedpath = ftp_strdup(path); 5411.50Slukem url_decode(decodedpath); 5421.50Slukem 5431.22Slukem if (outfile) 5441.166Schristos savefile = ftp_strdup(outfile); 5451.22Slukem else { 5461.50Slukem cp = strrchr(decodedpath, '/'); /* find savefile */ 5471.29Slukem if (cp != NULL) 5481.166Schristos savefile = ftp_strdup(cp + 1); 5491.22Slukem else 5501.166Schristos savefile = ftp_strdup(decodedpath); 5511.22Slukem } 5521.9Slukem if (EMPTYSTRING(savefile)) { 5531.42Slukem if (urltype == FTP_URL_T) { 5541.52Slukem rval = fetch_ftp(url); 5551.42Slukem goto cleanup_fetch_url; 5561.42Slukem } 5571.175Slukem warnx("No file after directory (you must specify an " 5581.140Sgrant "output file) `%s'", url); 5591.42Slukem goto cleanup_fetch_url; 5601.52Slukem } else { 5611.163Schristos DPRINTF("savefile `%s'\n", savefile); 5621.9Slukem } 5631.1Slukem 5641.95Slukem restart_point = 0; 5651.25Slukem filesize = -1; 5661.95Slukem rangestart = rangeend = entitylen = -1; 5671.25Slukem mtime = -1; 5681.95Slukem if (restartautofetch) { 5691.95Slukem if (strcmp(savefile, "-") != 0 && *savefile != '|' && 5701.95Slukem stat(savefile, &sb) == 0) 5711.95Slukem restart_point = sb.st_size; 5721.95Slukem } 5731.25Slukem if (urltype == FILE_URL_T) { /* file:// URLs */ 5741.25Slukem direction = "copied"; 5751.50Slukem fin = fopen(decodedpath, "r"); 5761.25Slukem if (fin == NULL) { 5771.175Slukem warn("Can't open `%s'", decodedpath); 5781.42Slukem goto cleanup_fetch_url; 5791.5Slukem } 5801.25Slukem if (fstat(fileno(fin), &sb) == 0) { 5811.25Slukem mtime = sb.st_mtime; 5821.25Slukem filesize = sb.st_size; 5831.25Slukem } 5841.95Slukem if (restart_point) { 5851.95Slukem if (lseek(fileno(fin), restart_point, SEEK_SET) < 0) { 5861.175Slukem warn("Can't seek to restart `%s'", 5871.95Slukem decodedpath); 5881.95Slukem goto cleanup_fetch_url; 5891.95Slukem } 5901.95Slukem } 5911.95Slukem if (verbose) { 5921.95Slukem fprintf(ttyout, "Copying %s", decodedpath); 5931.95Slukem if (restart_point) 5941.121Slukem fprintf(ttyout, " (restarting at " LLF ")", 5951.121Slukem (LLT)restart_point); 5961.95Slukem fputs("\n", ttyout); 5971.95Slukem } 5981.25Slukem } else { /* ftp:// or http:// URLs */ 5991.65Slukem char *leading; 6001.65Slukem int hasleading; 6011.65Slukem 6021.42Slukem if (proxyenv == NULL) { 6031.42Slukem if (urltype == HTTP_URL_T) 6041.91Slukem proxyenv = getoptionvalue("http_proxy"); 6051.42Slukem else if (urltype == FTP_URL_T) 6061.91Slukem proxyenv = getoptionvalue("ftp_proxy"); 6071.42Slukem } 6081.25Slukem direction = "retrieved"; 6091.91Slukem if (! EMPTYSTRING(proxyenv)) { /* use proxy */ 6101.27Slukem url_t purltype; 6111.54Slukem char *phost, *ppath; 6121.91Slukem char *pport, *no_proxy; 6131.27Slukem 6141.27Slukem isproxy = 1; 6151.27Slukem 6161.27Slukem /* check URL against list of no_proxied sites */ 6171.91Slukem no_proxy = getoptionvalue("no_proxy"); 6181.91Slukem if (! EMPTYSTRING(no_proxy)) { 6191.171Selad char *np, *np_copy, *np_iter; 6201.27Slukem long np_port; 6211.27Slukem size_t hlen, plen; 6221.27Slukem 6231.171Selad np_iter = np_copy = ftp_strdup(no_proxy); 6241.27Slukem hlen = strlen(host); 6251.171Selad while ((cp = strsep(&np_iter, " ,")) != NULL) { 6261.27Slukem if (*cp == '\0') 6271.27Slukem continue; 6281.60Sitojun if ((np = strrchr(cp, ':')) != NULL) { 6291.27Slukem *np = '\0'; 6301.27Slukem np_port = 6311.27Slukem strtol(np + 1, &ep, 10); 6321.27Slukem if (*ep != '\0') 6331.27Slukem continue; 6341.82Slukem if (np_port != portnum) 6351.27Slukem continue; 6361.27Slukem } 6371.27Slukem plen = strlen(cp); 6381.109Slukem if (hlen < plen) 6391.109Slukem continue; 6401.27Slukem if (strncasecmp(host + hlen - plen, 6411.27Slukem cp, plen) == 0) { 6421.27Slukem isproxy = 0; 6431.27Slukem break; 6441.27Slukem } 6451.27Slukem } 6461.27Slukem FREEPTR(np_copy); 6471.127Stacha if (isproxy == 0 && urltype == FTP_URL_T) { 6481.127Stacha rval = fetch_ftp(url); 6491.127Stacha goto cleanup_fetch_url; 6501.127Stacha } 6511.25Slukem } 6521.1Slukem 6531.27Slukem if (isproxy) { 6541.180Slukem if (restart_point) { 6551.180Slukem warnx("Can't restart via proxy URL `%s'", 6561.180Slukem proxyenv); 6571.180Slukem goto cleanup_fetch_url; 6581.180Slukem } 6591.27Slukem if (parse_url(proxyenv, "proxy URL", &purltype, 6601.82Slukem &puser, &ppass, &phost, &pport, &portnum, 6611.82Slukem &ppath) == -1) 6621.42Slukem goto cleanup_fetch_url; 6631.27Slukem 6641.27Slukem if ((purltype != HTTP_URL_T 6651.27Slukem && purltype != FTP_URL_T) || 6661.27Slukem EMPTYSTRING(phost) || 6671.27Slukem (! EMPTYSTRING(ppath) 6681.27Slukem && strcmp(ppath, "/") != 0)) { 6691.27Slukem warnx("Malformed proxy URL `%s'", 6701.27Slukem proxyenv); 6711.27Slukem FREEPTR(phost); 6721.60Sitojun FREEPTR(pport); 6731.27Slukem FREEPTR(ppath); 6741.42Slukem goto cleanup_fetch_url; 6751.27Slukem } 6761.112Sitojun if (isipv6addr(host) && 6771.112Sitojun strchr(host, '%') != NULL) { 6781.112Sitojun warnx( 6791.112Sitojun"Scoped address notation `%s' disallowed via web proxy", 6801.112Sitojun host); 6811.112Sitojun FREEPTR(phost); 6821.112Sitojun FREEPTR(pport); 6831.112Sitojun FREEPTR(ppath); 6841.112Sitojun goto cleanup_fetch_url; 6851.112Sitojun } 6861.12Slukem 6871.27Slukem FREEPTR(host); 6881.27Slukem host = phost; 6891.62Stron FREEPTR(port); 6901.62Stron port = pport; 6911.27Slukem FREEPTR(path); 6921.166Schristos path = ftp_strdup(url); 6931.27Slukem FREEPTR(ppath); 6941.27Slukem } 6951.91Slukem } /* ! EMPTYSTRING(proxyenv) */ 6961.25Slukem 6971.60Sitojun memset(&hints, 0, sizeof(hints)); 6981.65Slukem hints.ai_flags = 0; 6991.131Slukem hints.ai_family = family; 7001.60Sitojun hints.ai_socktype = SOCK_STREAM; 7011.60Sitojun hints.ai_protocol = 0; 7021.122Slukem error = getaddrinfo(host, NULL, &hints, &res0); 7031.60Sitojun if (error) { 7041.175Slukem warnx("Can't lookup `%s': %s", host, 7051.175Slukem (error == EAI_SYSTEM) ? strerror(errno) 7061.175Slukem : gai_strerror(error)); 7071.60Sitojun goto cleanup_fetch_url; 7081.60Sitojun } 7091.98Sitojun if (res0->ai_canonname) 7101.98Sitojun host = res0->ai_canonname; 7111.60Sitojun 7121.98Sitojun s = -1; 7131.98Sitojun for (res = res0; res; res = res->ai_next) { 7141.113Sitojun ai_unmapped(res); 7151.98Sitojun if (getnameinfo(res->ai_addr, res->ai_addrlen, 7161.142Sitojun hbuf, sizeof(hbuf), NULL, 0, NI_NUMERICHOST) != 0) 7171.175Slukem strlcpy(hbuf, "?", sizeof(hbuf)); 7181.98Sitojun 7191.161Slukem if (verbose && res0->ai_next) { 7201.98Sitojun fprintf(ttyout, "Trying %s...\n", hbuf); 7211.161Slukem } 7221.98Sitojun 7231.122Slukem ((struct sockaddr_in *)res->ai_addr)->sin_port = 7241.122Slukem htons(portnum); 7251.125Slukem s = socket(res->ai_family, SOCK_STREAM, 7261.125Slukem res->ai_protocol); 7271.60Sitojun if (s < 0) { 7281.175Slukem warn( 7291.175Slukem "Can't create socket for connection to `%s'", 7301.175Slukem hbuf); 7311.98Sitojun continue; 7321.60Sitojun } 7331.60Sitojun 7341.166Schristos if (ftp_connect(s, res->ai_addr, res->ai_addrlen) < 0) { 7351.60Sitojun close(s); 7361.98Sitojun s = -1; 7371.98Sitojun continue; 7381.60Sitojun } 7391.98Sitojun 7401.98Sitojun /* success */ 7411.60Sitojun break; 7421.60Sitojun } 7431.98Sitojun 7441.98Sitojun if (s < 0) { 7451.175Slukem warnx("Can't connect to `%s'", host); 7461.98Sitojun goto cleanup_fetch_url; 7471.98Sitojun } 7481.24Slukem 7491.25Slukem fin = fdopen(s, "r+"); 7501.25Slukem /* 7511.27Slukem * Construct and send the request. 7521.25Slukem */ 7531.65Slukem if (verbose) 7541.65Slukem fprintf(ttyout, "Requesting %s\n", url); 7551.65Slukem leading = " ("; 7561.65Slukem hasleading = 0; 7571.27Slukem if (isproxy) { 7581.65Slukem if (verbose) { 7591.65Slukem fprintf(ttyout, "%svia %s:%s", leading, 7601.65Slukem host, port); 7611.65Slukem leading = ", "; 7621.65Slukem hasleading++; 7631.65Slukem } 7641.44Slukem fprintf(fin, "GET %s HTTP/1.0\r\n", path); 7651.47Slukem if (flushcache) 7661.47Slukem fprintf(fin, "Pragma: no-cache\r\n"); 7671.27Slukem } else { 7681.27Slukem fprintf(fin, "GET %s HTTP/1.1\r\n", path); 7691.106Sitojun if (strchr(host, ':')) { 7701.112Sitojun char *h, *p; 7711.112Sitojun 7721.112Sitojun /* 7731.112Sitojun * strip off IPv6 scope identifier, since it is 7741.112Sitojun * local to the node 7751.112Sitojun */ 7761.166Schristos h = ftp_strdup(host); 7771.112Sitojun if (isipv6addr(h) && 7781.112Sitojun (p = strchr(h, '%')) != NULL) { 7791.112Sitojun *p = '\0'; 7801.112Sitojun } 7811.134Slukem fprintf(fin, "Host: [%s]", h); 7821.112Sitojun free(h); 7831.106Sitojun } else 7841.134Slukem fprintf(fin, "Host: %s", host); 7851.134Slukem if (portnum != HTTP_PORT) 7861.134Slukem fprintf(fin, ":%u", portnum); 7871.134Slukem fprintf(fin, "\r\n"); 7881.40Sexplorer fprintf(fin, "Accept: */*\r\n"); 7891.42Slukem fprintf(fin, "Connection: close\r\n"); 7901.95Slukem if (restart_point) { 7911.95Slukem fputs(leading, ttyout); 7921.121Slukem fprintf(fin, "Range: bytes=" LLF "-\r\n", 7931.121Slukem (LLT)restart_point); 7941.121Slukem fprintf(ttyout, "restarting at " LLF, 7951.121Slukem (LLT)restart_point); 7961.95Slukem leading = ", "; 7971.95Slukem hasleading++; 7981.95Slukem } 7991.47Slukem if (flushcache) 8001.47Slukem fprintf(fin, "Cache-Control: no-cache\r\n"); 8011.25Slukem } 8021.144Slukem if ((useragent=getenv("FTPUSERAGENT")) != NULL) { 8031.144Slukem fprintf(fin, "User-Agent: %s\r\n", useragent); 8041.144Slukem } else { 8051.144Slukem fprintf(fin, "User-Agent: %s/%s\r\n", 8061.144Slukem FTP_PRODUCT, FTP_VERSION); 8071.144Slukem } 8081.44Slukem if (wwwauth) { 8091.65Slukem if (verbose) { 8101.65Slukem fprintf(ttyout, "%swith authorization", 8111.65Slukem leading); 8121.65Slukem leading = ", "; 8131.65Slukem hasleading++; 8141.65Slukem } 8151.44Slukem fprintf(fin, "Authorization: %s\r\n", wwwauth); 8161.44Slukem } 8171.46Slukem if (proxyauth) { 8181.65Slukem if (verbose) { 8191.49Slukem fprintf(ttyout, 8201.65Slukem "%swith proxy authorization", leading); 8211.65Slukem leading = ", "; 8221.65Slukem hasleading++; 8231.65Slukem } 8241.44Slukem fprintf(fin, "Proxy-Authorization: %s\r\n", proxyauth); 8251.46Slukem } 8261.65Slukem if (verbose && hasleading) 8271.65Slukem fputs(")\n", ttyout); 8281.44Slukem fprintf(fin, "\r\n"); 8291.25Slukem if (fflush(fin) == EOF) { 8301.25Slukem warn("Writing HTTP request"); 8311.42Slukem goto cleanup_fetch_url; 8321.25Slukem } 8331.1Slukem 8341.25Slukem /* Read the response */ 8351.176Slukem len = getline(fin, buf, sizeof(buf), &errormsg); 8361.176Slukem if (len < 0) { 8371.176Slukem if (*errormsg == '\n') 8381.176Slukem errormsg++; 8391.176Slukem warnx("Receiving HTTP reply: %s", errormsg); 8401.42Slukem goto cleanup_fetch_url; 8411.24Slukem } 8421.150Slukem while (len > 0 && (ISLWS(buf[len-1]))) 8431.24Slukem buf[--len] = '\0'; 8441.163Schristos DPRINTF("received `%s'\n", buf); 8451.1Slukem 8461.42Slukem /* Determine HTTP response code */ 8471.25Slukem cp = strchr(buf, ' '); 8481.25Slukem if (cp == NULL) 8491.25Slukem goto improper; 8501.25Slukem else 8511.25Slukem cp++; 8521.42Slukem hcode = strtol(cp, &ep, 10); 8531.42Slukem if (*ep != '\0' && !isspace((unsigned char)*ep)) 8541.42Slukem goto improper; 8551.166Schristos message = ftp_strdup(cp); 8561.25Slukem 8571.25Slukem /* Read the rest of the header. */ 8581.25Slukem while (1) { 8591.176Slukem len = getline(fin, buf, sizeof(buf), &errormsg); 8601.176Slukem if (len < 0) { 8611.176Slukem if (*errormsg == '\n') 8621.176Slukem errormsg++; 8631.176Slukem warnx("Receiving HTTP reply: %s", errormsg); 8641.42Slukem goto cleanup_fetch_url; 8651.25Slukem } 8661.150Slukem while (len > 0 && (ISLWS(buf[len-1]))) 8671.25Slukem buf[--len] = '\0'; 8681.25Slukem if (len == 0) 8691.25Slukem break; 8701.163Schristos DPRINTF("received `%s'\n", buf); 8711.25Slukem 8721.154Slukem /* 8731.154Slukem * Look for some headers 8741.154Slukem */ 8751.154Slukem 8761.25Slukem cp = buf; 8771.42Slukem 8781.154Slukem if (match_token(&cp, "Content-Length:")) { 8791.119Slukem filesize = STRTOLL(cp, &ep, 10); 8801.95Slukem if (filesize < 0 || *ep != '\0') 8811.25Slukem goto improper; 8821.163Schristos DPRINTF("parsed len as: " LLF "\n", 8831.163Schristos (LLT)filesize); 8841.42Slukem 8851.154Slukem } else if (match_token(&cp, "Content-Range:")) { 8861.154Slukem if (! match_token(&cp, "bytes")) 8871.150Slukem goto improper; 8881.154Slukem 8891.154Slukem if (*cp == '*') 8901.154Slukem cp++; 8911.128Syamt else { 8921.128Syamt rangestart = STRTOLL(cp, &ep, 10); 8931.128Syamt if (rangestart < 0 || *ep != '-') 8941.128Syamt goto improper; 8951.128Syamt cp = ep + 1; 8961.128Syamt rangeend = STRTOLL(cp, &ep, 10); 8971.128Syamt if (rangeend < 0 || rangeend < rangestart) 8981.128Syamt goto improper; 8991.154Slukem cp = ep; 9001.128Syamt } 9011.154Slukem if (*cp != '/') 9021.95Slukem goto improper; 9031.154Slukem cp++; 9041.154Slukem if (*cp == '*') 9051.154Slukem cp++; 9061.128Syamt else { 9071.128Syamt entitylen = STRTOLL(cp, &ep, 10); 9081.128Syamt if (entitylen < 0) 9091.128Syamt goto improper; 9101.154Slukem cp = ep; 9111.128Syamt } 9121.154Slukem if (*cp != '\0') 9131.95Slukem goto improper; 9141.95Slukem 9151.163Schristos#ifndef NO_DEBUG 9161.167Schristos if (ftp_debug) { 9171.128Syamt fprintf(ttyout, "parsed range as: "); 9181.128Syamt if (rangestart == -1) 9191.128Syamt fprintf(ttyout, "*"); 9201.128Syamt else 9211.128Syamt fprintf(ttyout, LLF "-" LLF, 9221.128Syamt (LLT)rangestart, 9231.128Syamt (LLT)rangeend); 9241.128Syamt fprintf(ttyout, "/" LLF "\n", (LLT)entitylen); 9251.128Syamt } 9261.163Schristos#endif 9271.95Slukem if (! restart_point) { 9281.95Slukem warnx( 9291.95Slukem "Received unexpected Content-Range header"); 9301.95Slukem goto cleanup_fetch_url; 9311.95Slukem } 9321.95Slukem 9331.154Slukem } else if (match_token(&cp, "Last-Modified:")) { 9341.25Slukem struct tm parsed; 9351.25Slukem char *t; 9361.25Slukem 9371.178Slukem memset(&parsed, 0, sizeof(parsed)); 9381.177Slukem /* RFC1123 */ 9391.25Slukem if ((t = strptime(cp, 9401.25Slukem "%a, %d %b %Y %H:%M:%S GMT", 9411.25Slukem &parsed)) 9421.177Slukem /* RFC0850 */ 9431.25Slukem || (t = strptime(cp, 9441.25Slukem "%a, %d-%b-%y %H:%M:%S GMT", 9451.25Slukem &parsed)) 9461.25Slukem /* asctime */ 9471.25Slukem || (t = strptime(cp, 9481.25Slukem "%a, %b %d %H:%M:%S %Y", 9491.25Slukem &parsed))) { 9501.38Slukem parsed.tm_isdst = -1; 9511.25Slukem if (*t == '\0') 9521.76Slukem mtime = timegm(&parsed); 9531.163Schristos#ifndef NO_DEBUG 9541.167Schristos if (ftp_debug && mtime != -1) { 9551.25Slukem fprintf(ttyout, 9561.25Slukem "parsed date as: %s", 9571.179Slukem rfc2822time(localtime(&mtime))); 9581.38Slukem } 9591.163Schristos#endif 9601.25Slukem } 9611.42Slukem 9621.154Slukem } else if (match_token(&cp, "Location:")) { 9631.166Schristos location = ftp_strdup(cp); 9641.163Schristos DPRINTF("parsed location as `%s'\n", cp); 9651.44Slukem 9661.154Slukem } else if (match_token(&cp, "Transfer-Encoding:")) { 9671.154Slukem if (match_token(&cp, "binary")) { 9681.109Slukem warnx( 9691.175Slukem "Bogus transfer encoding `binary' (fetching anyway)"); 9701.109Slukem continue; 9711.109Slukem } 9721.154Slukem if (! (token = match_token(&cp, "chunked"))) { 9731.45Slukem warnx( 9741.175Slukem "Unsupported transfer encoding `%s'", 9751.154Slukem token); 9761.45Slukem goto cleanup_fetch_url; 9771.45Slukem } 9781.45Slukem ischunked++; 9791.163Schristos DPRINTF("using chunked encoding\n"); 9801.45Slukem 9811.154Slukem } else if (match_token(&cp, "Proxy-Authenticate:") 9821.154Slukem || match_token(&cp, "WWW-Authenticate:")) { 9831.154Slukem if (! (token = match_token(&cp, "Basic"))) { 9841.163Schristos DPRINTF( 9851.154Slukem "skipping unknown auth scheme `%s'\n", 9861.154Slukem token); 9871.154Slukem continue; 9881.154Slukem } 9891.44Slukem FREEPTR(auth); 9901.166Schristos auth = ftp_strdup(token); 9911.163Schristos DPRINTF("parsed auth as `%s'\n", cp); 9921.25Slukem } 9931.44Slukem 9941.24Slukem } 9951.54Slukem /* finished parsing header */ 9961.1Slukem 9971.52Slukem switch (hcode) { 9981.52Slukem case 200: 9991.52Slukem break; 10001.95Slukem case 206: 10011.95Slukem if (! restart_point) { 10021.95Slukem warnx("Not expecting partial content header"); 10031.95Slukem goto cleanup_fetch_url; 10041.95Slukem } 10051.95Slukem break; 10061.52Slukem case 300: 10071.52Slukem case 301: 10081.52Slukem case 302: 10091.52Slukem case 303: 10101.52Slukem case 305: 10111.165Schristos case 307: 10121.52Slukem if (EMPTYSTRING(location)) { 10131.52Slukem warnx( 10141.52Slukem "No redirection Location provided by server"); 10151.52Slukem goto cleanup_fetch_url; 10161.52Slukem } 10171.52Slukem if (redirect_loop++ > 5) { 10181.52Slukem warnx("Too many redirections requested"); 10191.52Slukem goto cleanup_fetch_url; 10201.52Slukem } 10211.52Slukem if (hcode == 305) { 10221.52Slukem if (verbose) 10231.52Slukem fprintf(ttyout, "Redirected via %s\n", 10241.52Slukem location); 10251.52Slukem rval = fetch_url(url, location, 10261.52Slukem proxyauth, wwwauth); 10271.52Slukem } else { 10281.52Slukem if (verbose) 10291.52Slukem fprintf(ttyout, "Redirected to %s\n", 10301.52Slukem location); 10311.52Slukem rval = go_fetch(location); 10321.52Slukem } 10331.42Slukem goto cleanup_fetch_url; 10341.147Schristos#ifndef NO_AUTH 10351.52Slukem case 401: 10361.52Slukem case 407: 10371.52Slukem { 10381.52Slukem char **authp; 10391.54Slukem char *auser, *apass; 10401.52Slukem 10411.54Slukem if (hcode == 401) { 10421.54Slukem authp = &wwwauth; 10431.54Slukem auser = user; 10441.54Slukem apass = pass; 10451.54Slukem } else { 10461.54Slukem authp = &proxyauth; 10471.54Slukem auser = puser; 10481.54Slukem apass = ppass; 10491.54Slukem } 10501.149Slukem if (verbose || *authp == NULL || 10511.149Slukem auser == NULL || apass == NULL) 10521.149Slukem fprintf(ttyout, "%s\n", message); 10531.149Slukem if (EMPTYSTRING(auth)) { 10541.149Slukem warnx( 10551.149Slukem "No authentication challenge provided by server"); 10561.149Slukem goto cleanup_fetch_url; 10571.149Slukem } 10581.52Slukem if (*authp != NULL) { 10591.52Slukem char reply[10]; 10601.52Slukem 10611.52Slukem fprintf(ttyout, 10621.52Slukem "Authorization failed. Retry (y/n)? "); 10631.162Slukem if (getline(stdin, reply, sizeof(reply), NULL) 10641.162Slukem < 0) { 10651.52Slukem goto cleanup_fetch_url; 10661.90Slukem } 10671.153Sdsl if (tolower((unsigned char)reply[0]) != 'y') 10681.153Sdsl goto cleanup_fetch_url; 10691.54Slukem auser = NULL; 10701.54Slukem apass = NULL; 10711.52Slukem } 10721.54Slukem if (auth_url(auth, authp, auser, apass) == 0) { 10731.52Slukem rval = fetch_url(url, proxyenv, 10741.52Slukem proxyauth, wwwauth); 10751.80Slukem memset(*authp, 0, strlen(*authp)); 10761.52Slukem FREEPTR(*authp); 10771.52Slukem } 10781.42Slukem goto cleanup_fetch_url; 10791.52Slukem } 10801.147Schristos#endif 10811.52Slukem default: 10821.52Slukem if (message) 10831.175Slukem warnx("Error retrieving file `%s'", message); 10841.52Slukem else 10851.52Slukem warnx("Unknown error retrieving file"); 10861.44Slukem goto cleanup_fetch_url; 10871.44Slukem } 10881.52Slukem } /* end of ftp:// or http:// specific setup */ 10891.42Slukem 10901.22Slukem /* Open the output file. */ 10911.22Slukem if (strcmp(savefile, "-") == 0) { 10921.22Slukem fout = stdout; 10931.22Slukem } else if (*savefile == '|') { 10941.77Slukem oldintp = xsignal(SIGPIPE, SIG_IGN); 10951.22Slukem fout = popen(savefile + 1, "w"); 10961.22Slukem if (fout == NULL) { 10971.175Slukem warn("Can't execute `%s'", savefile + 1); 10981.42Slukem goto cleanup_fetch_url; 10991.22Slukem } 11001.22Slukem closefunc = pclose; 11011.22Slukem } else { 11021.129Syamt if ((rangeend != -1 && rangeend <= restart_point) || 11031.129Syamt (rangestart == -1 && filesize != -1 && filesize <= restart_point)) { 11041.129Syamt /* already done */ 11051.129Syamt if (verbose) 11061.129Syamt fprintf(ttyout, "already done\n"); 11071.129Syamt rval = 0; 11081.129Syamt goto cleanup_fetch_url; 11091.129Syamt } 11101.129Syamt if (restart_point && rangestart != -1) { 11111.95Slukem if (entitylen != -1) 11121.95Slukem filesize = entitylen; 11131.129Syamt if (rangestart != restart_point) { 11141.95Slukem warnx( 11151.95Slukem "Size of `%s' differs from save file `%s'", 11161.95Slukem url, savefile); 11171.95Slukem goto cleanup_fetch_url; 11181.95Slukem } 11191.95Slukem fout = fopen(savefile, "a"); 11201.95Slukem } else 11211.95Slukem fout = fopen(savefile, "w"); 11221.22Slukem if (fout == NULL) { 11231.27Slukem warn("Can't open `%s'", savefile); 11241.42Slukem goto cleanup_fetch_url; 11251.22Slukem } 11261.22Slukem closefunc = fclose; 11271.1Slukem } 11281.1Slukem 11291.22Slukem /* Trap signals */ 11301.88Slukem if (sigsetjmp(httpabort, 1)) 11311.42Slukem goto cleanup_fetch_url; 11321.86Slukem (void)xsignal(SIGQUIT, psummary); 11331.77Slukem oldintr = xsignal(SIGINT, aborthttp); 11341.2Slukem 11351.73Slukem if (rcvbuf_size > bufsize) { 11361.73Slukem if (xferbuf) 11371.73Slukem (void)free(xferbuf); 11381.73Slukem bufsize = rcvbuf_size; 11391.166Schristos xferbuf = ftp_malloc(bufsize); 11401.73Slukem } 11411.73Slukem 11421.1Slukem bytes = 0; 11431.1Slukem hashbytes = mark; 11441.1Slukem progressmeter(-1); 11451.2Slukem 11461.24Slukem /* Finally, suck down the file. */ 11471.45Slukem do { 11481.77Slukem long chunksize; 11491.181Slukem short lastchunk; 11501.45Slukem 11511.45Slukem chunksize = 0; 11521.181Slukem lastchunk = 0; 11531.181Slukem /* read chunk-size */ 11541.45Slukem if (ischunked) { 11551.73Slukem if (fgets(xferbuf, bufsize, fin) == NULL) { 11561.181Slukem warnx("Unexpected EOF reading chunk-size"); 11571.45Slukem goto cleanup_fetch_url; 11581.45Slukem } 11591.181Slukem errno = 0; 11601.73Slukem chunksize = strtol(xferbuf, &ep, 16); 11611.181Slukem if (ep == xferbuf) { 11621.181Slukem warnx("Invalid chunk-size"); 11631.181Slukem goto cleanup_fetch_url; 11641.181Slukem } 11651.181Slukem if (errno == ERANGE || chunksize < 0) { 11661.181Slukem errno = ERANGE; 11671.181Slukem warn("Chunk-size `%.*s'", 11681.182Slukem (int)(ep-xferbuf), xferbuf); 11691.181Slukem goto cleanup_fetch_url; 11701.181Slukem } 11711.103Slukem 11721.103Slukem /* 11731.123Slukem * XXX: Work around bug in Apache 1.3.9 and 11741.123Slukem * 1.3.11, which incorrectly put trailing 11751.181Slukem * space after the chunk-size. 11761.103Slukem */ 11771.123Slukem while (*ep == ' ') 11781.103Slukem ep++; 11791.103Slukem 11801.181Slukem /* skip [ chunk-ext ] */ 11811.181Slukem if (*ep == ';') { 11821.181Slukem while (*ep && *ep != '\r') 11831.181Slukem ep++; 11841.181Slukem } 11851.181Slukem 11861.45Slukem if (strcmp(ep, "\r\n") != 0) { 11871.181Slukem warnx("Unexpected data following chunk-size"); 11881.45Slukem goto cleanup_fetch_url; 11891.45Slukem } 11901.181Slukem DPRINTF("got chunk-size of " LLF "\n", (LLT)chunksize); 11911.181Slukem if (chunksize == 0) { 11921.181Slukem lastchunk = 1; 11931.181Slukem goto chunkdone; 11941.181Slukem } 11951.45Slukem } 11961.59Slukem /* transfer file or chunk */ 11971.59Slukem while (1) { 11981.59Slukem struct timeval then, now, td; 11991.59Slukem off_t bufrem; 12001.59Slukem 12011.71Slukem if (rate_get) 12021.71Slukem (void)gettimeofday(&then, NULL); 12031.73Slukem bufrem = rate_get ? rate_get : bufsize; 12041.101Slukem if (ischunked) 12051.101Slukem bufrem = MIN(chunksize, bufrem); 12061.59Slukem while (bufrem > 0) { 12071.73Slukem len = fread(xferbuf, sizeof(char), 12081.101Slukem MIN(bufsize, bufrem), fin); 12091.59Slukem if (len <= 0) 12101.59Slukem goto chunkdone; 12111.59Slukem bytes += len; 12121.59Slukem bufrem -= len; 12131.73Slukem if (fwrite(xferbuf, sizeof(char), len, fout) 12141.59Slukem != len) { 12151.59Slukem warn("Writing `%s'", savefile); 12161.59Slukem goto cleanup_fetch_url; 12171.59Slukem } 12181.101Slukem if (hash && !progress) { 12191.101Slukem while (bytes >= hashbytes) { 12201.101Slukem (void)putc('#', ttyout); 12211.101Slukem hashbytes += mark; 12221.101Slukem } 12231.101Slukem (void)fflush(ttyout); 12241.101Slukem } 12251.101Slukem if (ischunked) { 12261.101Slukem chunksize -= len; 12271.101Slukem if (chunksize <= 0) 12281.101Slukem break; 12291.45Slukem } 12301.59Slukem } 12311.59Slukem if (rate_get) { 12321.59Slukem while (1) { 12331.59Slukem (void)gettimeofday(&now, NULL); 12341.59Slukem timersub(&now, &then, &td); 12351.59Slukem if (td.tv_sec > 0) 12361.59Slukem break; 12371.59Slukem usleep(1000000 - td.tv_usec); 12381.59Slukem } 12391.59Slukem } 12401.101Slukem if (ischunked && chunksize <= 0) 12411.101Slukem break; 12421.1Slukem } 12431.45Slukem /* read CRLF after chunk*/ 12441.59Slukem chunkdone: 12451.45Slukem if (ischunked) { 12461.181Slukem if (fgets(xferbuf, bufsize, fin) == NULL) { 12471.181Slukem warnx("Unexpected EOF reading chunk CRLF"); 12481.181Slukem goto cleanup_fetch_url; 12491.181Slukem } 12501.73Slukem if (strcmp(xferbuf, "\r\n") != 0) { 12511.45Slukem warnx("Unexpected data following chunk"); 12521.45Slukem goto cleanup_fetch_url; 12531.1Slukem } 12541.181Slukem if (lastchunk) 12551.181Slukem break; 12561.1Slukem } 12571.45Slukem } while (ischunked); 12581.181Slukem 12591.181Slukem/* XXX: deal with optional trailer & CRLF here? */ 12601.181Slukem 12611.1Slukem if (hash && !progress && bytes > 0) { 12621.1Slukem if (bytes < mark) 12631.22Slukem (void)putc('#', ttyout); 12641.22Slukem (void)putc('\n', ttyout); 12651.1Slukem } 12661.25Slukem if (ferror(fin)) { 12671.25Slukem warn("Reading file"); 12681.42Slukem goto cleanup_fetch_url; 12691.1Slukem } 12701.2Slukem progressmeter(1); 12711.25Slukem (void)fflush(fout); 12721.25Slukem if (closefunc == fclose && mtime != -1) { 12731.25Slukem struct timeval tval[2]; 12741.25Slukem 12751.25Slukem (void)gettimeofday(&tval[0], NULL); 12761.25Slukem tval[1].tv_sec = mtime; 12771.25Slukem tval[1].tv_usec = 0; 12781.31Slukem (*closefunc)(fout); 12791.31Slukem fout = NULL; 12801.31Slukem 12811.31Slukem if (utimes(savefile, tval) == -1) { 12821.25Slukem fprintf(ttyout, 12831.25Slukem "Can't change modification time to %s", 12841.179Slukem rfc2822time(localtime(&mtime))); 12851.25Slukem } 12861.25Slukem } 12871.25Slukem if (bytes > 0) 12881.25Slukem ptransfer(0); 12891.136Slukem bytes = 0; 12901.1Slukem 12911.42Slukem rval = 0; 12921.42Slukem goto cleanup_fetch_url; 12931.11Slukem 12941.118Slukem improper: 12951.27Slukem warnx("Improper response from `%s'", host); 12961.11Slukem 12971.118Slukem cleanup_fetch_url: 12981.86Slukem if (oldintr) 12991.86Slukem (void)xsignal(SIGINT, oldintr); 13001.86Slukem if (oldintp) 13011.86Slukem (void)xsignal(SIGPIPE, oldintp); 13021.24Slukem if (fin != NULL) 13031.24Slukem fclose(fin); 13041.24Slukem else if (s != -1) 13051.1Slukem close(s); 13061.22Slukem if (closefunc != NULL && fout != NULL) 13071.22Slukem (*closefunc)(fout); 13081.156Slukem if (res0) 13091.156Slukem freeaddrinfo(res0); 13101.29Slukem FREEPTR(savefile); 13111.27Slukem FREEPTR(user); 13121.162Slukem if (pass != NULL) 13131.162Slukem memset(pass, 0, strlen(pass)); 13141.27Slukem FREEPTR(pass); 13151.27Slukem FREEPTR(host); 13161.60Sitojun FREEPTR(port); 13171.27Slukem FREEPTR(path); 13181.50Slukem FREEPTR(decodedpath); 13191.54Slukem FREEPTR(puser); 13201.162Slukem if (ppass != NULL) 13211.162Slukem memset(ppass, 0, strlen(ppass)); 13221.54Slukem FREEPTR(ppass); 13231.44Slukem FREEPTR(auth); 13241.42Slukem FREEPTR(location); 13251.42Slukem FREEPTR(message); 13261.42Slukem return (rval); 13271.1Slukem} 13281.1Slukem 13291.1Slukem/* 13301.50Slukem * Abort a HTTP retrieval 13311.2Slukem */ 13321.2Slukemvoid 13331.111Slukemaborthttp(int notused) 13341.2Slukem{ 13351.88Slukem char msgbuf[100]; 13361.159Slukem size_t len; 13371.2Slukem 13381.148Slukem sigint_raised = 1; 13391.2Slukem alarmtimer(0); 13401.88Slukem len = strlcpy(msgbuf, "\nHTTP fetch aborted.\n", sizeof(msgbuf)); 13411.88Slukem write(fileno(ttyout), msgbuf, len); 13421.88Slukem siglongjmp(httpabort, 1); 13431.2Slukem} 13441.2Slukem 13451.2Slukem/* 13461.50Slukem * Retrieve ftp URL or classic ftp argument using FTP. 13471.63Slukem * Returns 1 on failure, 0 on completed xfer, -1 if ftp connection 13481.42Slukem * is still open (e.g, ftp xfer with trailing /) 13491.42Slukem */ 13501.42Slukemstatic int 13511.111Slukemfetch_ftp(const char *url) 13521.42Slukem{ 13531.42Slukem char *cp, *xargv[5], rempath[MAXPATHLEN]; 13541.42Slukem char *host, *path, *dir, *file, *user, *pass; 13551.60Sitojun char *port; 13561.160Slukem int dirhasglob, filehasglob, rval, type, xargc; 13571.160Slukem int oanonftp, oautologin; 13581.82Slukem in_port_t portnum; 13591.82Slukem url_t urltype; 13601.42Slukem 13611.42Slukem host = path = dir = file = user = pass = NULL; 13621.60Sitojun port = NULL; 13631.42Slukem rval = 1; 13641.53Slukem type = TYPE_I; 13651.42Slukem 13661.150Slukem if (STRNEQUAL(url, FTP_URL)) { 13671.42Slukem if ((parse_url(url, "URL", &urltype, &user, &pass, 13681.82Slukem &host, &port, &portnum, &path) == -1) || 13691.42Slukem (user != NULL && *user == '\0') || 13701.42Slukem EMPTYSTRING(host)) { 13711.42Slukem warnx("Invalid URL `%s'", url); 13721.42Slukem goto cleanup_fetch_ftp; 13731.42Slukem } 13741.53Slukem /* 13751.53Slukem * Note: Don't url_decode(path) here. We need to keep the 13761.53Slukem * distinction between "/" and "%2F" until later. 13771.53Slukem */ 13781.53Slukem 13791.53Slukem /* check for trailing ';type=[aid]' */ 13801.63Slukem if (! EMPTYSTRING(path) && (cp = strrchr(path, ';')) != NULL) { 13811.53Slukem if (strcasecmp(cp, ";type=a") == 0) 13821.53Slukem type = TYPE_A; 13831.53Slukem else if (strcasecmp(cp, ";type=i") == 0) 13841.53Slukem type = TYPE_I; 13851.53Slukem else if (strcasecmp(cp, ";type=d") == 0) { 13861.53Slukem warnx( 13871.53Slukem "Directory listing via a URL is not supported"); 13881.53Slukem goto cleanup_fetch_ftp; 13891.53Slukem } else { 13901.53Slukem warnx("Invalid suffix `%s' in URL `%s'", cp, 13911.53Slukem url); 13921.53Slukem goto cleanup_fetch_ftp; 13931.53Slukem } 13941.53Slukem *cp = 0; 13951.53Slukem } 13961.97Slukem } else { /* classic style `[user@]host:[file]' */ 13971.53Slukem urltype = CLASSIC_URL_T; 13981.166Schristos host = ftp_strdup(url); 13991.97Slukem cp = strchr(host, '@'); 14001.97Slukem if (cp != NULL) { 14011.97Slukem *cp = '\0'; 14021.97Slukem user = host; 14031.97Slukem anonftp = 0; /* disable anonftp */ 14041.166Schristos host = ftp_strdup(cp + 1); 14051.97Slukem } 14061.42Slukem cp = strchr(host, ':'); 14071.42Slukem if (cp != NULL) { 14081.42Slukem *cp = '\0'; 14091.166Schristos path = ftp_strdup(cp + 1); 14101.42Slukem } 14111.42Slukem } 14121.42Slukem if (EMPTYSTRING(host)) 14131.42Slukem goto cleanup_fetch_ftp; 14141.42Slukem 14151.50Slukem /* Extract the file and (if present) directory name. */ 14161.42Slukem dir = path; 14171.42Slukem if (! EMPTYSTRING(dir)) { 14181.53Slukem /* 14191.97Slukem * If we are dealing with classic `[user@]host:[path]' syntax, 14201.97Slukem * then a path of the form `/file' (resulting from input of the 14211.97Slukem * form `host:/file') means that we should do "CWD /" before 14221.97Slukem * retrieving the file. So we set dir="/" and file="file". 14231.53Slukem * 14241.97Slukem * But if we are dealing with URLs like `ftp://host/path' then 14251.97Slukem * a path of the form `/file' (resulting from a URL of the form 14261.97Slukem * `ftp://host//file') means that we should do `CWD ' (with an 14271.97Slukem * empty argument) before retrieving the file. So we set 14281.53Slukem * dir="" and file="file". 14291.53Slukem * 14301.97Slukem * If the path does not contain / at all, we set dir=NULL. 14311.97Slukem * (We get a path without any slashes if we are dealing with 14321.97Slukem * classic `[user@]host:[file]' or URL `ftp://host/file'.) 14331.53Slukem * 14341.97Slukem * In all other cases, we set dir to a string that does not 14351.97Slukem * include the final '/' that separates the dir part from the 14361.97Slukem * file part of the path. (This will be the empty string if 14371.97Slukem * and only if we are dealing with a path of the form `/file' 14381.97Slukem * resulting from an URL of the form `ftp://host//file'.) 14391.53Slukem */ 14401.42Slukem cp = strrchr(dir, '/'); 14411.53Slukem if (cp == dir && urltype == CLASSIC_URL_T) { 14421.50Slukem file = cp + 1; 14431.50Slukem dir = "/"; 14441.50Slukem } else if (cp != NULL) { 14451.42Slukem *cp++ = '\0'; 14461.42Slukem file = cp; 14471.42Slukem } else { 14481.42Slukem file = dir; 14491.42Slukem dir = NULL; 14501.42Slukem } 14511.63Slukem } else 14521.63Slukem dir = NULL; 14531.53Slukem if (urltype == FTP_URL_T && file != NULL) { 14541.157Slukem url_decode(file); 14551.53Slukem /* but still don't url_decode(dir) */ 14561.53Slukem } 14571.163Schristos DPRINTF("fetch_ftp: user `%s' pass `%s' host %s port %s " 14581.163Schristos "path `%s' dir `%s' file `%s'\n", 14591.163Schristos user ? user : "<null>", pass ? pass : "<null>", 14601.163Schristos host ? host : "<null>", port ? port : "<null>", 14611.163Schristos path ? path : "<null>", 14621.163Schristos dir ? dir : "<null>", file ? file : "<null>"); 14631.42Slukem 14641.42Slukem dirhasglob = filehasglob = 0; 14651.53Slukem if (doglob && urltype == CLASSIC_URL_T) { 14661.42Slukem if (! EMPTYSTRING(dir) && strpbrk(dir, "*?[]{}") != NULL) 14671.42Slukem dirhasglob = 1; 14681.42Slukem if (! EMPTYSTRING(file) && strpbrk(file, "*?[]{}") != NULL) 14691.42Slukem filehasglob = 1; 14701.42Slukem } 14711.42Slukem 14721.50Slukem /* Set up the connection */ 14731.160Slukem oanonftp = anonftp; 14741.50Slukem if (connected) 14751.50Slukem disconnect(0, NULL); 14761.160Slukem anonftp = oanonftp; 14771.126Scgd xargv[0] = (char *)getprogname(); /* XXX discards const */ 14781.50Slukem xargv[1] = host; 14791.50Slukem xargv[2] = NULL; 14801.50Slukem xargc = 2; 14811.50Slukem if (port) { 14821.60Sitojun xargv[2] = port; 14831.50Slukem xargv[3] = NULL; 14841.50Slukem xargc = 3; 14851.50Slukem } 14861.50Slukem oautologin = autologin; 14871.116Slukem /* don't autologin in setpeer(), use ftp_login() below */ 14881.116Slukem autologin = 0; 14891.50Slukem setpeer(xargc, xargv); 14901.50Slukem autologin = oautologin; 14911.116Slukem if ((connected == 0) || 14921.116Slukem (connected == 1 && !ftp_login(host, user, pass))) { 14931.50Slukem warnx("Can't connect or login to host `%s'", host); 14941.50Slukem goto cleanup_fetch_ftp; 14951.50Slukem } 14961.42Slukem 14971.53Slukem switch (type) { 14981.53Slukem case TYPE_A: 14991.118Slukem setascii(1, xargv); 15001.53Slukem break; 15011.53Slukem case TYPE_I: 15021.118Slukem setbinary(1, xargv); 15031.53Slukem break; 15041.53Slukem default: 15051.91Slukem errx(1, "fetch_ftp: unknown transfer type %d", type); 15061.53Slukem } 15071.53Slukem 15081.63Slukem /* 15091.63Slukem * Change directories, if necessary. 15101.63Slukem * 15111.63Slukem * Note: don't use EMPTYSTRING(dir) below, because 15121.63Slukem * dir=="" means something different from dir==NULL. 15131.63Slukem */ 15141.53Slukem if (dir != NULL && !dirhasglob) { 15151.53Slukem char *nextpart; 15161.42Slukem 15171.53Slukem /* 15181.97Slukem * If we are dealing with a classic `[user@]host:[path]' 15191.97Slukem * (urltype is CLASSIC_URL_T) then we have a raw directory 15201.53Slukem * name (not encoded in any way) and we can change 15211.53Slukem * directories in one step. 15221.53Slukem * 15231.53Slukem * If we are dealing with an `ftp://host/path' URL 15241.177Slukem * (urltype is FTP_URL_T), then RFC3986 says we need to 15251.53Slukem * send a separate CWD command for each unescaped "/" 15261.53Slukem * in the path, and we have to interpret %hex escaping 15271.53Slukem * *after* we find the slashes. It's possible to get 15281.53Slukem * empty components here, (from multiple adjacent 15291.177Slukem * slashes in the path) and RFC3986 says that we should 15301.53Slukem * still do `CWD ' (with a null argument) in such cases. 15311.53Slukem * 15321.63Slukem * Many ftp servers don't support `CWD ', so if there's an 15331.63Slukem * error performing that command, bail out with a descriptive 15341.63Slukem * message. 15351.53Slukem * 15361.53Slukem * Examples: 15371.53Slukem * 15381.63Slukem * host: dir="", urltype=CLASSIC_URL_T 15391.63Slukem * logged in (to default directory) 15401.53Slukem * host:file dir=NULL, urltype=CLASSIC_URL_T 15411.53Slukem * "RETR file" 15421.63Slukem * host:dir/ dir="dir", urltype=CLASSIC_URL_T 15431.63Slukem * "CWD dir", logged in 15441.63Slukem * ftp://host/ dir="", urltype=FTP_URL_T 15451.63Slukem * logged in (to default directory) 15461.63Slukem * ftp://host/dir/ dir="dir", urltype=FTP_URL_T 15471.63Slukem * "CWD dir", logged in 15481.53Slukem * ftp://host/file dir=NULL, urltype=FTP_URL_T 15491.53Slukem * "RETR file" 15501.53Slukem * ftp://host//file dir="", urltype=FTP_URL_T 15511.63Slukem * "CWD ", "RETR file" 15521.53Slukem * host:/file dir="/", urltype=CLASSIC_URL_T 15531.53Slukem * "CWD /", "RETR file" 15541.53Slukem * ftp://host///file dir="/", urltype=FTP_URL_T 15551.63Slukem * "CWD ", "CWD ", "RETR file" 15561.53Slukem * ftp://host/%2F/file dir="%2F", urltype=FTP_URL_T 15571.53Slukem * "CWD /", "RETR file" 15581.53Slukem * ftp://host/foo/file dir="foo", urltype=FTP_URL_T 15591.53Slukem * "CWD foo", "RETR file" 15601.53Slukem * ftp://host/foo/bar/file dir="foo/bar" 15611.53Slukem * "CWD foo", "CWD bar", "RETR file" 15621.53Slukem * ftp://host//foo/bar/file dir="/foo/bar" 15631.63Slukem * "CWD ", "CWD foo", "CWD bar", "RETR file" 15641.53Slukem * ftp://host/foo//bar/file dir="foo//bar" 15651.63Slukem * "CWD foo", "CWD ", "CWD bar", "RETR file" 15661.53Slukem * ftp://host/%2F/foo/bar/file dir="%2F/foo/bar" 15671.53Slukem * "CWD /", "CWD foo", "CWD bar", "RETR file" 15681.53Slukem * ftp://host/%2Ffoo/bar/file dir="%2Ffoo/bar" 15691.53Slukem * "CWD /foo", "CWD bar", "RETR file" 15701.53Slukem * ftp://host/%2Ffoo%2Fbar/file dir="%2Ffoo%2Fbar" 15711.53Slukem * "CWD /foo/bar", "RETR file" 15721.53Slukem * ftp://host/%2Ffoo%2Fbar%2Ffile dir=NULL 15731.53Slukem * "RETR /foo/bar/file" 15741.53Slukem * 15751.53Slukem * Note that we don't need `dir' after this point. 15761.53Slukem */ 15771.53Slukem do { 15781.53Slukem if (urltype == FTP_URL_T) { 15791.53Slukem nextpart = strchr(dir, '/'); 15801.53Slukem if (nextpart) { 15811.53Slukem *nextpart = '\0'; 15821.53Slukem nextpart++; 15831.53Slukem } 15841.53Slukem url_decode(dir); 15851.53Slukem } else 15861.53Slukem nextpart = NULL; 15871.163Schristos DPRINTF("dir `%s', nextpart `%s'\n", 15881.163Schristos dir ? dir : "<null>", 15891.163Schristos nextpart ? nextpart : "<null>"); 15901.63Slukem if (urltype == FTP_URL_T || *dir != '\0') { 15911.53Slukem xargv[0] = "cd"; 15921.53Slukem xargv[1] = dir; 15931.53Slukem xargv[2] = NULL; 15941.53Slukem dirchange = 0; 15951.53Slukem cd(2, xargv); 15961.63Slukem if (! dirchange) { 15971.63Slukem if (*dir == '\0' && code == 500) 15981.63Slukem fprintf(stderr, 15991.63Slukem"\n" 16001.63Slukem"ftp: The `CWD ' command (without a directory), which is required by\n" 16011.177Slukem" RFC3986 to support the empty directory in the URL pathname (`//'),\n" 16021.177Slukem" conflicts with the server's conformance to RFC0959.\n" 16031.63Slukem" Try the same URL without the `//' in the URL pathname.\n" 16041.63Slukem"\n"); 16051.53Slukem goto cleanup_fetch_ftp; 16061.63Slukem } 16071.53Slukem } 16081.53Slukem dir = nextpart; 16091.53Slukem } while (dir != NULL); 16101.42Slukem } 16111.42Slukem 16121.42Slukem if (EMPTYSTRING(file)) { 16131.42Slukem rval = -1; 16141.42Slukem goto cleanup_fetch_ftp; 16151.42Slukem } 16161.42Slukem 16171.42Slukem if (dirhasglob) { 16181.78Slukem (void)strlcpy(rempath, dir, sizeof(rempath)); 16191.78Slukem (void)strlcat(rempath, "/", sizeof(rempath)); 16201.78Slukem (void)strlcat(rempath, file, sizeof(rempath)); 16211.42Slukem file = rempath; 16221.42Slukem } 16231.42Slukem 16241.42Slukem /* Fetch the file(s). */ 16251.42Slukem xargc = 2; 16261.42Slukem xargv[0] = "get"; 16271.42Slukem xargv[1] = file; 16281.42Slukem xargv[2] = NULL; 16291.42Slukem if (dirhasglob || filehasglob) { 16301.42Slukem int ointeractive; 16311.42Slukem 16321.42Slukem ointeractive = interactive; 16331.42Slukem interactive = 0; 16341.155Slukem if (restartautofetch) 16351.155Slukem xargv[0] = "mreget"; 16361.155Slukem else 16371.155Slukem xargv[0] = "mget"; 16381.42Slukem mget(xargc, xargv); 16391.42Slukem interactive = ointeractive; 16401.42Slukem } else { 16411.53Slukem if (outfile == NULL) { 16421.53Slukem cp = strrchr(file, '/'); /* find savefile */ 16431.53Slukem if (cp != NULL) 16441.53Slukem outfile = cp + 1; 16451.53Slukem else 16461.53Slukem outfile = file; 16471.42Slukem } 16481.53Slukem xargv[2] = (char *)outfile; 16491.53Slukem xargv[3] = NULL; 16501.53Slukem xargc++; 16511.52Slukem if (restartautofetch) 16521.52Slukem reget(xargc, xargv); 16531.52Slukem else 16541.52Slukem get(xargc, xargv); 16551.42Slukem } 16561.42Slukem 16571.42Slukem if ((code / 100) == COMPLETE) 16581.42Slukem rval = 0; 16591.42Slukem 16601.118Slukem cleanup_fetch_ftp: 16611.169Schristos FREEPTR(port); 16621.42Slukem FREEPTR(host); 16631.42Slukem FREEPTR(path); 16641.42Slukem FREEPTR(user); 16651.162Slukem if (pass) 16661.162Slukem memset(pass, 0, strlen(pass)); 16671.42Slukem FREEPTR(pass); 16681.42Slukem return (rval); 16691.42Slukem} 16701.42Slukem 16711.42Slukem/* 16721.42Slukem * Retrieve the given file to outfile. 16731.42Slukem * Supports arguments of the form: 16741.42Slukem * "host:path", "ftp://host/path" if $ftpproxy, call fetch_url() else 16751.42Slukem * call fetch_ftp() 16761.50Slukem * "http://host/path" call fetch_url() to use HTTP 16771.42Slukem * "file:///path" call fetch_url() to copy 16781.42Slukem * "about:..." print a message 16791.42Slukem * 16801.42Slukem * Returns 1 on failure, 0 on completed xfer, -1 if ftp connection 16811.42Slukem * is still open (e.g, ftp xfer with trailing /) 16821.42Slukem */ 16831.42Slukemstatic int 16841.111Slukemgo_fetch(const char *url) 16851.42Slukem{ 16861.91Slukem char *proxy; 16871.42Slukem 16881.147Schristos#ifndef NO_ABOUT 16891.42Slukem /* 16901.42Slukem * Check for about:* 16911.42Slukem */ 16921.150Slukem if (STRNEQUAL(url, ABOUT_URL)) { 16931.42Slukem url += sizeof(ABOUT_URL) -1; 16941.144Slukem if (strcasecmp(url, "ftp") == 0 || 16951.144Slukem strcasecmp(url, "tnftp") == 0) { 16961.87Slukem fputs( 16971.143Ssalo"This version of ftp has been enhanced by Luke Mewburn <lukem@NetBSD.org>\n" 16981.87Slukem"for the NetBSD project. Execute `man ftp' for more details.\n", ttyout); 16991.87Slukem } else if (strcasecmp(url, "lukem") == 0) { 17001.87Slukem fputs( 17011.87Slukem"Luke Mewburn is the author of most of the enhancements in this ftp client.\n" 17021.143Ssalo"Please email feedback to <lukem@NetBSD.org>.\n", ttyout); 17031.42Slukem } else if (strcasecmp(url, "netbsd") == 0) { 17041.87Slukem fputs( 17051.87Slukem"NetBSD is a freely available and redistributable UNIX-like operating system.\n" 17061.144Slukem"For more information, see http://www.NetBSD.org/\n", ttyout); 17071.92Slukem } else if (strcasecmp(url, "version") == 0) { 17081.115Slukem fprintf(ttyout, "Version: %s %s%s\n", 17091.115Slukem FTP_PRODUCT, FTP_VERSION, 17101.115Slukem#ifdef INET6 17111.115Slukem "" 17121.115Slukem#else 17131.115Slukem " (-IPv6)" 17141.115Slukem#endif 17151.115Slukem ); 17161.42Slukem } else { 17171.42Slukem fprintf(ttyout, "`%s' is an interesting topic.\n", url); 17181.42Slukem } 17191.92Slukem fputs("\n", ttyout); 17201.42Slukem return (0); 17211.42Slukem } 17221.147Schristos#endif 17231.42Slukem 17241.42Slukem /* 17251.42Slukem * Check for file:// and http:// URLs. 17261.42Slukem */ 17271.150Slukem if (STRNEQUAL(url, HTTP_URL) || STRNEQUAL(url, FILE_URL)) 17281.52Slukem return (fetch_url(url, NULL, NULL, NULL)); 17291.42Slukem 17301.42Slukem /* 17311.42Slukem * Try FTP URL-style and host:file arguments next. 17321.42Slukem * If ftpproxy is set with an FTP URL, use fetch_url() 17331.42Slukem * Othewise, use fetch_ftp(). 17341.42Slukem */ 17351.91Slukem proxy = getoptionvalue("ftp_proxy"); 17361.150Slukem if (!EMPTYSTRING(proxy) && STRNEQUAL(url, FTP_URL)) 17371.52Slukem return (fetch_url(url, NULL, NULL, NULL)); 17381.42Slukem 17391.52Slukem return (fetch_ftp(url)); 17401.42Slukem} 17411.42Slukem 17421.42Slukem/* 17431.42Slukem * Retrieve multiple files from the command line, 17441.42Slukem * calling go_fetch() for each file. 17451.42Slukem * 17461.42Slukem * If an ftp path has a trailing "/", the path will be cd-ed into and 17471.41Slukem * the connection remains open, and the function will return -1 17481.41Slukem * (to indicate the connection is alive). 17491.1Slukem * If an error occurs the return value will be the offset+1 in 17501.1Slukem * argv[] of the file that caused a problem (i.e, argv[x] 17511.1Slukem * returns x+1) 17521.1Slukem * Otherwise, 0 is returned if all files retrieved successfully. 17531.1Slukem */ 17541.1Slukemint 17551.111Slukemauto_fetch(int argc, char *argv[]) 17561.1Slukem{ 17571.160Slukem volatile int argpos, rval; 17581.22Slukem 17591.160Slukem argpos = rval = 0; 17601.1Slukem 17611.88Slukem if (sigsetjmp(toplevel, 1)) { 17621.2Slukem if (connected) 17631.2Slukem disconnect(0, NULL); 17641.148Slukem if (rval > 0) 17651.148Slukem rval = argpos + 1; 17661.148Slukem return (rval); 17671.2Slukem } 17681.88Slukem (void)xsignal(SIGINT, intr); 17691.91Slukem (void)xsignal(SIGPIPE, lostpeer); 17701.1Slukem 17711.1Slukem /* 17721.1Slukem * Loop through as long as there's files to fetch. 17731.1Slukem */ 17741.160Slukem for (; (rval == 0) && (argpos < argc); argpos++) { 17751.1Slukem if (strchr(argv[argpos], ':') == NULL) 17761.1Slukem break; 17771.42Slukem redirect_loop = 0; 17781.90Slukem if (!anonftp) 17791.90Slukem anonftp = 2; /* Handle "automatic" transfers. */ 17801.52Slukem rval = go_fetch(argv[argpos]); 17811.52Slukem if (outfile != NULL && strcmp(outfile, "-") != 0 17821.52Slukem && outfile[0] != '|') 17831.52Slukem outfile = NULL; 17841.42Slukem if (rval > 0) 17851.1Slukem rval = argpos + 1; 17861.42Slukem } 17871.3Slukem 17881.1Slukem if (connected && rval != -1) 17891.1Slukem disconnect(0, NULL); 17901.114Slukem return (rval); 17911.114Slukem} 17921.114Slukem 17931.114Slukem 17941.170Slukem/* 17951.170Slukem * Upload multiple files from the command line. 17961.170Slukem * 17971.170Slukem * If an error occurs the return value will be the offset+1 in 17981.170Slukem * argv[] of the file that caused a problem (i.e, argv[x] 17991.170Slukem * returns x+1) 18001.170Slukem * Otherwise, 0 is returned if all files uploaded successfully. 18011.170Slukem */ 18021.114Slukemint 18031.114Slukemauto_put(int argc, char **argv, const char *uploadserver) 18041.114Slukem{ 18051.114Slukem char *uargv[4], *path, *pathsep; 18061.170Slukem int uargc, rval, argpos; 18071.159Slukem size_t len; 18081.114Slukem 18091.114Slukem uargc = 0; 18101.114Slukem uargv[uargc++] = "mput"; 18111.114Slukem uargv[uargc++] = argv[0]; 18121.114Slukem uargv[2] = uargv[3] = NULL; 18131.114Slukem pathsep = NULL; 18141.114Slukem rval = 1; 18151.114Slukem 18161.163Schristos DPRINTF("auto_put: target `%s'\n", uploadserver); 18171.114Slukem 18181.166Schristos path = ftp_strdup(uploadserver); 18191.114Slukem len = strlen(path); 18201.114Slukem if (path[len - 1] != '/' && path[len - 1] != ':') { 18211.114Slukem /* 18221.114Slukem * make sure we always pass a directory to auto_fetch 18231.114Slukem */ 18241.114Slukem if (argc > 1) { /* more than one file to upload */ 18251.114Slukem len = strlen(uploadserver) + 2; /* path + "/" + "\0" */ 18261.114Slukem free(path); 18271.166Schristos path = (char *)ftp_malloc(len); 18281.114Slukem (void)strlcpy(path, uploadserver, len); 18291.114Slukem (void)strlcat(path, "/", len); 18301.114Slukem } else { /* single file to upload */ 18311.114Slukem uargv[0] = "put"; 18321.114Slukem pathsep = strrchr(path, '/'); 18331.114Slukem if (pathsep == NULL) { 18341.114Slukem pathsep = strrchr(path, ':'); 18351.114Slukem if (pathsep == NULL) { 18361.114Slukem warnx("Invalid URL `%s'", path); 18371.114Slukem goto cleanup_auto_put; 18381.114Slukem } 18391.114Slukem pathsep++; 18401.166Schristos uargv[2] = ftp_strdup(pathsep); 18411.114Slukem pathsep[0] = '/'; 18421.157Slukem } else 18431.166Schristos uargv[2] = ftp_strdup(pathsep + 1); 18441.114Slukem pathsep[1] = '\0'; 18451.114Slukem uargc++; 18461.114Slukem } 18471.114Slukem } 18481.163Schristos DPRINTF("auto_put: URL `%s' argv[2] `%s'\n", 18491.163Schristos path, uargv[2] ? uargv[2] : "<null>"); 18501.157Slukem 18511.157Slukem /* connect and cwd */ 18521.114Slukem rval = auto_fetch(1, &path); 18531.114Slukem if(rval >= 0) 18541.114Slukem goto cleanup_auto_put; 18551.114Slukem 18561.170Slukem rval = 0; 18571.170Slukem 18581.170Slukem /* target filename provided; upload 1 file */ 18591.114Slukem /* XXX : is this the best way? */ 18601.114Slukem if (uargc == 3) { 18611.114Slukem uargv[1] = argv[0]; 18621.114Slukem put(uargc, uargv); 18631.170Slukem if ((code / 100) != COMPLETE) 18641.170Slukem rval = 1; 18651.170Slukem } else { /* otherwise a target dir: upload all files to it */ 18661.170Slukem for(argpos = 0; argv[argpos] != NULL; argpos++) { 18671.170Slukem uargv[1] = argv[argpos]; 18681.170Slukem mput(uargc, uargv); 18691.170Slukem if ((code / 100) != COMPLETE) { 18701.170Slukem rval = argpos + 1; 18711.170Slukem break; 18721.170Slukem } 18731.170Slukem } 18741.114Slukem } 18751.114Slukem 18761.118Slukem cleanup_auto_put: 18771.168Schristos free(path); 18781.114Slukem FREEPTR(uargv[2]); 18791.1Slukem return (rval); 18801.1Slukem} 1881